Compare commits

...

245 Commits
v1.60 ... v3.0

Author SHA1 Message Date
b37231d0f5 version 3.0 2020-07-26 01:33:02 +02:00
3c55719bf1 finalize repeat asmgen 2020-07-26 01:32:27 +02:00
af8279a9b9 empty for loops are removed 2020-07-25 22:54:50 +02:00
c38508c262 introduced repeat loop. repeat-until changed to do-util.
forever loop is gone (use repeat without iteration count).
struct literal is now same as array literal [...] to avoid parsing ambiguity with scope blocks.
2020-07-25 16:56:34 +02:00
b0e8738ab8 remove unused c64 resources 2020-07-25 14:47:31 +02:00
cae480768e version is work in progress 2020-07-25 14:45:06 +02:00
a70276c190 use indexOfFirst. Also avoid initializing a for loop variable twice in a row. 2020-07-25 14:44:24 +02:00
0c461ffe2e removed Register expression (directly accessing cpu register) 2020-07-25 14:14:24 +02:00
237511f2d6 v2.4 2020-07-04 18:56:47 +02:00
cdcb652033 optimized arg passing if all args are registers 2020-07-04 18:56:30 +02:00
71e678b382 fixed possible register subroutine arg clobbering 2020-07-04 17:05:36 +02:00
3050156325 reverted subroutine inlining, it was a mistake 2020-07-04 01:02:36 +02:00
4bfdbad2e4 added mandel gfx to examples 2020-07-03 23:56:36 +02:00
06137ecdc4 v2.3 2020-07-03 23:51:27 +02:00
d89f5b0df8 todo about fixing argclobbering 2020-07-03 23:49:17 +02:00
b6e2b36692 refactor 2020-07-03 23:37:38 +02:00
a6d789cfbc fixed function argument type cast bug 2020-07-03 17:24:43 +02:00
c07907e7bd fixed missing shifts codegen 2020-07-02 21:28:48 +02:00
7d8496c874 fixed missing shifts codegen 2020-07-02 19:18:47 +02:00
164ac56db1 compiler error todos 2020-07-01 22:31:38 +02:00
fdddb8ca64 slight optimization 2020-07-01 22:23:46 +02:00
a9d4b8b0fa fixed ast modifications on node arrays, in particular function call parameter lists 2020-07-01 22:03:54 +02:00
ec7b9f54c2 subroutine inlining is an optimizer step 2020-07-01 12:41:10 +02:00
307558a7e7 removed some double code related to call tree 2020-06-30 20:42:55 +02:00
febf423eab tehtriz compilation issues 2020-06-30 20:42:13 +02:00
a999c23014 simple subroutine inlining added 2020-06-27 17:03:03 +02:00
69f1ade595 gfx mandelbrot example added 2020-06-18 01:35:24 +02:00
b166576e54 comments 2020-06-17 23:27:54 +02:00
ee2ba5f398 some more optimizations for swap() function call asm code generation 2020-06-17 22:40:57 +02:00
cb9825484d some more optimized in-array assignments codegeneration 2020-06-17 21:41:38 +02:00
76cda82e23 v2.2 2020-06-16 01:43:44 +02:00
37b61d9e6b v2.2 2020-06-16 01:39:11 +02:00
52f0222a6d Got rid of old Ast transformer Api, some compiler error fixes 2020-06-16 01:25:49 +02:00
75ccac2f2c refactoring last of old Ast modification Api 2020-06-16 00:36:02 +02:00
5c771a91f7 refactoring last of old Ast modification Api 2020-06-14 16:56:48 +02:00
a242ad10e6 fix double printing of sub param vardecl 2020-06-14 13:46:46 +02:00
b5086b6a8f refactoring last of old Ast modification Api 2020-06-14 03:17:42 +02:00
3e47dad12a clearer no modifications 2020-06-14 02:54:29 +02:00
235610f40c refactored StatementOptimizer 2020-06-14 02:41:23 +02:00
6b59559c65 memory address assignment codegen 2020-06-14 02:12:40 +02:00
23e954f716 refactoring StatementOptimizer 2020-06-14 02:00:32 +02:00
983c899cad refactor AstIdentifierChecker 2020-06-13 00:14:19 +02:00
c2f9385965 refactor AstIdentifierChecker 2020-06-12 21:34:27 +02:00
ceb2c9e4f8 added string value assignment, leftstr, rightstr, substr functions 2020-06-06 00:05:39 +02:00
68a7f9c665 version 2.1 2020-06-04 23:03:18 +02:00
ffd8d9c7c1 more assignment expression optimizations 2020-06-04 22:57:32 +02:00
c66fc8630c fixed missing repeated constant folding in expression optimization 2020-06-04 20:22:37 +02:00
9ca1c66f2b added some optimizations for >= 0 and <0 comparisons for integers 2020-06-04 01:43:37 +02:00
33647a29d0 be smarter about certain implicit type casts 2020-06-03 23:55:41 +02:00
02b12cc762 optimized swap() for byte and word vars, optimized graphics line routine 2020-06-03 23:27:50 +02:00
3280993e2a stricter type checking in assignments (less implicit typecasts) 2020-06-02 22:36:57 +02:00
3723c22054 fix string param type 2020-06-02 02:09:52 +02:00
0a2c4ea0c4 improved ast printing 2020-06-02 01:51:27 +02:00
58a83c0439 improved code gen for passing string and array types. 2020-06-02 01:44:42 +02:00
d665489054 implemented asm for addressof-assignment 2020-06-02 00:31:56 +02:00
9200992024 slightly improved asm gen error messages 2020-06-02 00:31:20 +02:00
6408cc46a8 cmdrx16 github ref 2020-05-15 00:32:45 +02:00
961bcdb7ae some more todo's noted down 2020-05-15 00:24:25 +02:00
edee70cf31 use new api for ast mods in unused code remover 2020-05-15 00:16:53 +02:00
1978a9815a version 2.0 2020-05-14 23:59:18 +02:00
f5e6db9d66 big compiler speedup due to optimized scope lookups 2020-05-14 23:59:02 +02:00
a94bc40ab0 performance todo's 2020-05-08 20:41:10 +02:00
534b5ced8f updated the compiled examples 2020-04-10 23:36:29 +02:00
5ebd9b54e4 added some more optimized array assignments 2020-04-10 23:30:19 +02:00
cc4e272526 the new assignment code (once complete) really is a big enough change to bump the version to 2.0 2020-04-09 00:24:37 +02:00
295e199bfa optimized asm output for unneeded typecasts, fixed parent node linking issues with replaceChildNode, Assignment aug_op field is now mutable to avoid having to recreate many Assignment nodes 2020-04-09 00:12:50 +02:00
df3371b0f0 slight gfx optimizations 2020-04-08 22:53:23 +02:00
e4fe1d2b8d attempts to optimize in-place assignments 2020-04-08 03:11:38 +02:00
b8b9244ffa merged AddressOfInserter into StatementReorderer 2020-04-06 15:23:54 +02:00
3be3989e1c version 2020-04-06 14:31:23 +02:00
ed54cf680a fixed ast parent link bug in AstWalker, rewrote StatementReorderer using new API, when labels are sorted. 2020-04-06 14:31:02 +02:00
95e76058d3 version 2020-04-03 23:55:29 +02:00
a6bee6a860 some slight tweaks to asm for setting float value in array 2020-04-03 22:44:10 +02:00
d22780ee44 implemented asm for lsl array values 2020-04-03 21:45:52 +02:00
f8b0b9575d implemented asm for rol array values 2020-04-03 21:31:39 +02:00
4274fd168e implemented asm for rol2 array values 2020-04-03 21:24:55 +02:00
be7f5957f3 implemented asm for ror2 array values 2020-04-03 21:04:42 +02:00
f2e5d987a9 implemented asm for ror array values 2020-04-03 00:03:42 +02:00
f01173d8db fixed compilation of clear/set_carry() and clear/set_irqd() functions 2020-04-03 00:00:58 +02:00
15e8e0bf6d implemented asm for lsr array values 2020-04-02 23:38:45 +02:00
2c59cbdece fixed a crash in astchecking of array init values 2020-04-02 18:40:04 +02:00
b73da4ed02 some more obvious optimizations for X+X and X-X 2020-03-31 23:54:01 +02:00
267adb4612 doc 2020-03-29 03:06:51 +02:00
05c73fa8bc version 2020-03-28 17:06:59 +01:00
bfe9f442e6 balloon 2020-03-28 17:06:17 +01:00
0deadb694b updated the compiled examples 2020-03-28 14:31:31 +01:00
bed34378be doc 2020-03-28 14:24:00 +01:00
5927cf2d43 added turtle graphics example 2020-03-28 14:17:35 +01:00
fffe36e358 fix bresenham line 2020-03-28 13:42:24 +01:00
fac2a2d7cb fast asm plot 2020-03-28 00:36:44 +01:00
0af5582ca7 fix compiler crash for undefined symbol in expression 2020-03-27 23:09:46 +01:00
582d31263c better lines and circles 2020-03-27 00:09:17 +01:00
4108a528e1 proepr compiler error when there's no main module 2020-03-26 23:22:01 +01:00
ab7d7c2907 fix comparison of memory expressions (this error prevented some optimizations) 2020-03-26 22:59:42 +01:00
152888ee93 fix direcetmemoryread invalid asm 2020-03-26 22:46:05 +01:00
22f8f4f359 fixed memory pointer access asm code for direct reads and direct assignments 2020-03-26 19:20:39 +01:00
5f3a9e189a doc 2020-03-26 01:20:04 +01:00
b734dc44fd fix invalid assembly for @(address)++/-- 2020-03-26 01:13:20 +01:00
fab224f509 fix compiler crashing with invalid array initializer built from single integer 2020-03-25 01:23:54 +01:00
2f05ebb966 bitmap lines and circles 2020-03-25 01:07:42 +01:00
a335ba519a fix warnings about unreachable code 2020-03-24 22:37:42 +01:00
8805693ed2 cleaned up the way return statements are added to avoid code falling through in/out of subroutines 2020-03-24 22:02:50 +01:00
f2bb238e9b cleaned up various ast checks/mutations 2020-03-24 19:37:54 +01:00
131fe670a4 optimized scroll routines by removing needless twin loops 2020-03-24 17:33:47 +01:00
11e9539416 smooth scroll 2020-03-24 02:42:32 +01:00
3881ebe429 begun skramble clone 2020-03-24 01:47:02 +01:00
29d1b8802e whitespace 2020-03-24 00:24:51 +01:00
bcc75732e9 optimize asm jsr+rts into jmp 2020-03-23 23:51:27 +01:00
50a85ee6b0 attempt to optimize asm for bitshifts more. 2020-03-23 22:59:29 +01:00
2c7424fd43 fix: datatype mismatch in optimized bitshift const value 2020-03-23 22:35:23 +01:00
7426587c38 fix: add proper return statement type cast if needed, now also for non constant values 2020-03-23 19:49:11 +01:00
1f39749a5e tweak bitshift asm 2020-03-23 17:35:58 +01:00
ca63051c71 replaced todo's that aren't real todo's with regular exception 2020-03-23 13:00:44 +01:00
6dd44aaf0d compiler main cleanup 2020-03-23 02:54:04 +01:00
f89457ba68 fixed var initialization bug in anonymous scopes 2020-03-23 02:09:30 +01:00
efef205fcf doc 2020-03-23 01:24:54 +01:00
0c561d8528 fixed subroutine parameter value issue 2020-03-23 00:13:46 +01:00
8bfa2c4c02 proper initialization of block-level global variables 2020-03-22 22:47:05 +01:00
f0d4c3aba9 moved initialvalues to asmgen, fixed sgn bug and internal float 0.0 variable disappearing bug 2020-03-22 18:17:12 +01:00
3a99115070 Initial variable values semantics changed: now always sets value at program (re)start (except strings/arrays).
This may change later by introducing a compiler option to choose a strategy, perhaps.
2020-03-22 15:12:26 +01:00
7232134931 fix some compiler errors 2020-03-22 13:47:13 +01:00
954e911eb3 optimized zeros array initializer 2020-03-22 02:58:51 +01:00
63c073c93f got rid of the Simulator / AST VM 2020-03-22 02:50:34 +01:00
78feef9d59 simplified handling of initial vardecl values in codegeneration 2020-03-22 02:45:42 +01:00
4fbdd6d570 fix ubyte number print bug for 100-109 and 200-209 missing the tens digit 2020-03-22 01:49:05 +01:00
4929c198ba tweak error reporting, expanded lines and circles example 2020-03-22 00:43:46 +01:00
9409f17372 bugfixes in new optimization routines 2020-03-21 23:09:18 +01:00
43781c02d0 tweaked ast modifications 2020-03-21 18:42:40 +01:00
824f06e17f new var init values 2020-03-21 14:54:19 +01:00
21dbc6da97 doc 2020-03-21 12:51:32 +01:00
270ea54ff7 now properly compile assignment of struct literal value to struct variable (outside of vardecl) 2020-03-21 00:57:20 +01:00
771ac7aba7 error when struct literal value element count doesn't match struct members in assignment 2020-03-20 23:14:03 +01:00
97d36243f2 don't include the generated parser java files in git 2020-03-20 22:53:56 +01:00
511b47bac4 fix compiler crash when initializing struct var with something other than a struct literal 2020-03-20 22:48:33 +01:00
f265199fbe replaced typecastsAdder with version based on astwalker 2020-03-20 22:28:18 +01:00
a191ec71a4 this is not modifying the ast 2020-03-19 23:16:58 +01:00
82dce2dd53 added Foreverloop statement to the ast simulator 2020-03-19 22:45:27 +01:00
29ac160811 applying new astwalker for modifications 2020-03-19 22:40:49 +01:00
5e50ea14f8 applying new astwalker for modifications 2020-03-19 21:30:01 +01:00
40e6091506 new astvisitor tryout 2020-03-19 00:01:57 +01:00
0ee4d420b1 slight tweaks on the Ast, Program (the top level) is now a Node as well 2020-03-18 22:29:30 +01:00
66acce9e8e doc 2020-03-15 01:49:16 +01:00
6c23ae14ab ver 2020-03-15 01:37:01 +01:00
6f000d0d26 fix datatype warning 2020-03-15 01:14:44 +01:00
9d7eb3be5a fix error reporting of constantfolding, and number of errors printed 2020-03-15 01:10:08 +01:00
835555171e fix function call arg type mismatch crash 2020-03-15 00:50:59 +01:00
68ce4a1bf0 labels are now prefixed with underscore in assembly to fix undefined symbol errors from the assembler 2020-03-15 00:23:54 +01:00
a995867deb added check for duplicate label definitions 2020-03-15 00:16:50 +01:00
6bd99d63b4 cleanup of error reporting 2020-03-14 23:47:26 +01:00
baf5d3041a cleanup of error reporting 2020-03-14 23:15:44 +01:00
a326ffa00a added warning about sgn() of unsigned type 2020-03-14 21:09:34 +01:00
d28dd92b47 refreshed examples 2020-03-14 18:11:38 +01:00
1de328b2e8 added forever-loop and optimizer 2020-03-14 18:11:04 +01:00
51bb902162 added bresenham and circle example 2020-03-14 17:11:10 +01:00
4fd14f1366 doc updates 2020-03-14 15:20:04 +01:00
91d9559f79 avoid pulling in the dbus libraries for now 2020-03-14 14:40:39 +01:00
3245a9b157 restricted block to only directive/subroutine/vardecl/inlineasm 2020-03-14 14:20:55 +01:00
2b28493bba simplified module grammar rules 2020-03-14 13:44:13 +01:00
1382728bd2 warning about unreachable code after a return statement
added some dbus experiments for future compilation service
2020-03-14 13:12:01 +01:00
0422ad080a added exit function to astvm simulator 2020-03-13 02:44:01 +01:00
64d682bfde todo 2020-03-13 02:33:02 +01:00
b182f7e693 optimizer removes unreachable code following call to exit() 2020-03-13 02:31:53 +01:00
e6be428589 compiler warning for unreachable code following a call to exit() 2020-03-13 02:21:37 +01:00
85c7f8314b added exit(rc) builtin function to immediately exit the program with a return code in A register 2020-03-13 02:08:18 +01:00
796d07a7f8 fix crash in asm code generated for bitshift operation with memory address operand 2020-03-13 01:26:53 +01:00
2af86a10b2 remove stack error comments 2020-03-13 00:52:52 +01:00
7fbe486dff fix eval stack register X error in print_uw 2020-03-13 00:50:30 +01:00
87e5a9859a remove autogenerated labels from vice mon list, fixes #17 2020-03-12 22:33:58 +01:00
b036e5ed72 refreshed the compiled examples 2020-03-12 01:14:10 +01:00
5f1ec80ae0 improved array literal datatype handling, fixed some datatype compiler errors related to this 2020-03-12 01:10:19 +01:00
fbecedaf41 added error for unsupported sort(floatarray) 2020-03-11 23:33:06 +01:00
aa36acd65a implemented reverse(floatarray) builtin function 2020-03-11 23:18:03 +01:00
8d1a4588d3 added 'downto' range expression 2020-03-11 20:59:14 +01:00
66d2af4453 added '@' alternative string/char encoding 2020-03-11 00:41:58 +01:00
ef6c731bb3 added '@' alternative string/char encoding 2020-03-11 00:32:50 +01:00
98a638a2f3 split asmsub and romsub declarations 2020-03-10 23:09:31 +01:00
96d8a7f0d7 float assembly code moved to separate library file 2020-03-10 22:03:24 +01:00
3162b10392 optimize callgraph 2020-03-10 21:47:15 +01:00
e2358de27c ver 2020-03-10 20:39:30 +01:00
7facb4f372 correct version 1.70 2020-02-09 01:41:05 +01:00
ee90fed489 readme 2020-02-09 01:33:20 +01:00
4796c56c35 antlr code back 2020-02-09 01:29:58 +01:00
e2cb031386 added 'void' keyword to explicitly ignore subroutine return values (and no longer get a warning) 2020-02-09 01:29:09 +01:00
a0bc97b90c fix byte array iteration for bb in [1,2,3]
improved array literal datatype detection
2020-02-09 00:45:53 +01:00
fd240899bd fix CHROUT in simulator 2020-02-09 00:12:50 +01:00
885b22df40 fixed while and repeat warning messages line number
fixed invalid while and repeat asm label names
fixed boolean checking of numbers
2020-02-08 19:45:30 +01:00
11de3db25f simplified heapId for arrayvalues 2020-02-08 18:49:48 +01:00
14a13da7ec simplified heapId for stringvalue 2020-02-08 15:54:03 +01:00
875a71c786 removed datatype from StringValue classes (is always STR now) 2020-02-08 02:21:18 +01:00
0ff5b79353 code inspection cleanups 2020-02-08 01:31:41 +01:00
8c4d276810 improvements to string encoding/decoding and text output in the simulator 2020-02-08 01:12:30 +01:00
3dd38c0ac8 antlr library updated to 4.8 2020-02-07 23:58:07 +01:00
b8816a0e2f got rid of separate str_s datatype 2020-02-07 20:47:38 +01:00
a01a9e76f9 removed bogus clang target
fixed various simulator bugs regarding strings and chars
2020-02-07 01:22:07 +01:00
357d704aec clean up version specifier 2020-02-02 19:33:40 +01:00
868df1865c got rid of obsolete code 2020-02-02 19:18:40 +01:00
654d74da1e automatic selection of best Vice C64 emulator executable 2020-02-02 13:39:56 +01:00
59939c727a gradle updated 2020-02-02 13:39:25 +01:00
fbcf190324 sync gradle version with my manjaro packaged gradle 2020-01-27 21:32:42 +01:00
b9922a90cc update gradle wrapper to 6.1.1 2020-01-26 18:36:51 +01:00
66e0b07428 gradle updates 2020-01-07 01:29:25 +01:00
01e617ae8f new kotlin version 2019-12-09 16:17:20 +01:00
52769decd4 fix assembler float truncation warning 2019-11-27 22:36:59 +01:00
165eec4054 started a c++ language compiler code target
(meant to be an intermediate step before direct Wasm/binaryen, via clang compilation to wasm)
2019-10-30 00:15:03 +01:00
8c2e602cc7 preparing for multiple compiler backends/targets 2019-10-26 23:41:15 +02:00
b68f141568 some more old code cleanups 2019-10-21 00:12:26 +02:00
b5d1e8653d tiny cleanups 2019-10-20 23:52:26 +02:00
f6d4c90dea improved number-to-decimal routines 2019-09-23 20:44:41 +02:00
b5b24636ae removed sim65 because it was moved to a separate repository 2019-09-11 02:24:44 +02:00
9dedbbf47c use more modern java date/time api 2019-09-10 01:29:33 +02:00
c493c3e5c6 implemented IRQ handling 2019-09-09 23:28:41 +02:00
61d4ca1d24 added functional test files to git 2019-09-09 19:57:51 +02:00
2cf9af4a6e implemented sim timer and clock 2019-09-09 04:51:18 +02:00
bdcd10512f 6502 simulator passes all tests for regular opcodes 2019-09-09 00:27:06 +02:00
fec8db6a75 fixed sbc and adc 2019-09-08 22:35:08 +02:00
b400010426 separated the 6502 test suite into separate unit tests 2019-09-08 19:11:06 +02:00
28109a39ac clean up of c64 tests 2019-09-08 17:19:40 +02:00
651f0ec445 fixed IZY addressing mode address calc
added test harness for Wolfgang Lorenz's 6502 test suite
2019-09-08 16:40:46 +02:00
e61d3df380 added missing testfiles 2019-09-06 01:09:23 +02:00
15710207b2 fixed bcd (but the bcd test code still fails, strange) 2019-09-06 00:38:48 +02:00
adfddddac6 attempt to fix bcd 2019-09-05 21:38:40 +02:00
e46982f652 fixes 2019-09-05 01:41:48 +02:00
900c2aea23 fixed all instructions except BCD arithmetic 2019-09-05 01:26:01 +02:00
42f8e98cab cpu unit test suite ported from Py65 2019-09-04 22:23:31 +02:00
bed0e33b4f unit test 2019-09-04 02:41:09 +02:00
8d6542905d beginnings of 6502 cpu simulator 2019-09-03 23:58:46 +02:00
39798a1a4f todos 2019-08-29 22:31:29 +02:00
befe4b8e9f try to fix windows path issue with drive letter 2019-08-27 01:02:31 +02:00
772e48105e fixed some type cast compiler errors in for loops 2019-08-26 23:38:59 +02:00
9afe451b8d fix build script to target jdk 1.8 2019-08-26 21:27:45 +02:00
89d469e77e examples 2019-08-25 00:46:46 +02:00
59a43889a5 examples 2019-08-25 00:24:00 +02:00
7caa0daffc examples 2019-08-24 21:40:50 +02:00
5e854c2cf8 more forloop asm 2019-08-24 21:26:29 +02:00
9edc92ec29 more bitshift asm stubs (actual functions still to be done) 2019-08-23 23:06:36 +02:00
1d178080a3 more bitshift asm 2019-08-23 21:33:43 +02:00
aa94300bdd added output directory command line option
improved cli parser by using kotlinx.cli
2019-08-23 00:11:08 +02:00
2d768c3f28 code cleanups 2019-08-22 22:06:21 +02:00
b79af624ae added more asmgen for bitshift operations 2019-08-22 00:34:17 +02:00
38208a7c9e removed fake vm functions 2019-08-21 22:00:05 +02:00
8eff51904e taking down the heapvalue mess further 2019-08-21 00:29:31 +02:00
c717f4573d taking down the heapvalue mess further 2019-08-20 23:02:13 +02:00
984d251a6d taking down the heapvalue mess, RuntimeValue class separation 2019-08-20 00:01:31 +02:00
8c3b43f3ed taking down the heapvalue mess 2019-08-19 22:28:41 +02:00
0f1485f30b added sorted, sgn, reverse to the AstVm 2019-08-18 16:39:08 +02:00
197 changed files with 13036 additions and 27542 deletions

13
.gitignore vendored
View File

@ -1,8 +1,8 @@
.idea/workspace.xml
.idea/discord.xml
/build/
/dist/
/output/
build/
dist/
output/
.*cache/
*.directory
*.prg
@ -12,9 +12,9 @@
*.vice-mon-list
docs/build
out/
**/*.interp
**/*.tokens
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
*.py[cod]
*.egg
*.egg-info
@ -28,5 +28,4 @@ parsetab.py
.attach_pid*
.gradle
build/
/prog8compiler.jar

View File

@ -1,6 +1,16 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" />
<language isEnabled="false" name="ActionScript" />
</Languages>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />

9
.idea/libraries/antlr_4_8_complete.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-4.8-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

9
.idea/libraries/antlr_runtime_4_8.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-runtime-4.8">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

29
.idea/markdown-navigator-enh.xml generated Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
<component name="MarkdownNavigatorHistory">
<PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</PasteImageHistory>
<CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</CopyImageHistory>
<PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" />
<TableToJsonHistory>
<entries />
</TableToJsonHistory>
<TableSortHistory>
<entries />
</TableSortHistory>
</component>
</project>

57
.idea/markdown-navigator.xml generated Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.panel" providerName="JavaFX WebView" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ANCHORLINKS" value="true" />
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.generator" providerName="JavaFx HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.css" providerName="Default JavaFx Stylesheet" />
</StylesheetProvider>
<ScriptProviders>
<provider providerId="com.vladsch.md.nav.editor.hljs.html.script" providerName="HighlightJS Script" />
</ScriptProviders>
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>

16
.idea/misc.xml generated
View File

@ -1,5 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ANTLRGenerationPreferences">
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
<option name="autoGen" value="true" />
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
<option name="libDir" value="" />
<option name="encoding" value="" />
<option name="pkg" value="" />
<option name="language" value="" />
<option name="generateListener" value="false" />
</PerGrammarGenerationSettings>
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>

1
.idea/modules.xml generated
View File

@ -2,7 +2,6 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

2
.idea/vcs.xml generated
View File

@ -3,4 +3,4 @@
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +0,0 @@
package compiler.intermediate
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.Syscall
open class Instruction(val opcode: Opcode,
val arg: RuntimeValue? = null,
val arg2: RuntimeValue? = null,
val callLabel: String? = null,
val callLabel2: String? = null)
{
var branchAddress: Int? = null
override fun toString(): String {
val argStr = arg?.toString() ?: ""
val result =
when {
opcode== Opcode.LINE -> "_line $callLabel"
opcode== Opcode.INLINE_ASSEMBLY -> {
// inline assembly is not written out (it can't be processed as intermediate language)
// instead, it is converted into a system call that can be intercepted by the vm
if(callLabel!=null)
"syscall SYSASM.$callLabel\n return"
else
"inline_assembly"
}
opcode== Opcode.INCLUDE_FILE -> {
"include_file \"$callLabel\" $arg $arg2"
}
opcode== Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall"
}
opcode in opcodesWithVarArgument -> {
// opcodes that manipulate a variable
"${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
}
callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
}
.trimEnd()
return " $result"
}
}
class LabelInstr(val name: String, val asmProc: Boolean) : Instruction(Opcode.NOP, null, null) {
override fun toString(): String {
return "\n$name:"
}
}

View File

@ -1,548 +0,0 @@
package compiler.intermediate
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageDepletedError
import prog8.vm.RuntimeValue
import java.io.PrintStream
import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableList<Variable> = mutableListOf(),
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean)
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock
fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore???
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0
for(block in blocks) {
val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
if (zpVariables.isNotEmpty()) {
for (variable in zpVariables) {
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
throw CompilerException("zp conflict")
try {
val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
notAllocated++
}
}
}
}
if(notAllocated>0)
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
}
fun optimize() {
println("Optimizing stackVM code...")
// remove nops (that are not a label)
for (blk in blocks) {
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
}
optimizeDataConversionAndUselessDiscards()
optimizeVariableCopying()
optimizeMultipleSequentialLineInstrs()
optimizeCallReturnIntoJump()
optimizeConditionalBranches()
// todo: add more optimizations to intermediate code!
optimizeRemoveNops() // must be done as the last step
optimizeMultipleSequentialLineInstrs() // once more
optimizeRemoveNops() // once more
}
private fun optimizeConditionalBranches() {
// conditional branches that consume the value on the stack
// sometimes these are just constant values, so we can statically determine the branch
// or, they are preceded by a NOT instruction so we can simply remove that and flip the branch condition
val pushvalue = setOf(Opcode.PUSH_BYTE, Opcode.PUSH_WORD)
val notvalue = setOf(Opcode.NOT_BYTE, Opcode.NOT_WORD)
val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW)
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
if (it[1].value.opcode in branchOpcodes) {
if (it[0].value.opcode in pushvalue) {
val value = it[0].value.arg!!.asBoolean
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction =
if (value) {
when (it[1].value.opcode) {
Opcode.JNZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
Opcode.JNZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
} else {
when (it[1].value.opcode) {
Opcode.JZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
Opcode.JZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
}
instructionsToReplace[it[1].index] = replacement
}
else if (it[0].value.opcode in notvalue) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction =
when (it[1].value.opcode) {
Opcode.JZ -> Instruction(Opcode.JNZ, callLabel = it[1].value.callLabel)
Opcode.JZW -> Instruction(Opcode.JNZW, callLabel = it[1].value.callLabel)
Opcode.JNZ -> Instruction(Opcode.JZ, callLabel = it[1].value.callLabel)
Opcode.JNZW -> Instruction(Opcode.JZW, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
instructionsToReplace[it[1].index] = replacement
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeRemoveNops() {
// remove nops (that are not a label)
for (blk in blocks)
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
}
private fun optimizeCallReturnIntoJump() {
// replaces call X followed by return, by jump X
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
if(it[0].value.opcode== Opcode.CALL && it[1].value.opcode== Opcode.RETURN) {
instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeMultipleSequentialLineInstrs() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeVariableCopying() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
when (it[0].value.opcode) {
Opcode.PUSH_VAR_BYTE ->
if (it[1].value.opcode == Opcode.POP_VAR_BYTE) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_VAR_WORD ->
if (it[1].value.opcode == Opcode.POP_VAR_WORD) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_VAR_FLOAT ->
if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB ->
if(it[1].value.opcode == Opcode.POP_MEM_BYTE) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW ->
if(it[1].value.opcode == Opcode.POP_MEM_WORD) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_FLOAT ->
if(it[1].value.opcode == Opcode.POP_MEM_FLOAT) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
else -> {}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeDataConversionAndUselessDiscards() {
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
// - push something followed by a discard -> remove both
val instructionsToReplace = mutableMapOf<Int, Instruction>()
fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) {
if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
}
fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.DISCARD_FLOAT -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a float")
}
}
fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.CAST_UW_TO_B, Opcode.CAST_W_TO_B -> {
val ins = Instruction(Opcode.PUSH_BYTE, ins0.arg!!.cast(DataType.BYTE))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.MSB -> {
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_UW_TO_W -> {
val cv = ins0.arg!!.cast(DataType.WORD)
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_UW -> {
val cv = ins0.arg!!.cast(DataType.UWORD)
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a word")
}
}
fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.CAST_B_TO_UB, Opcode.CAST_UB_TO_B,
Opcode.CAST_W_TO_B, Opcode.CAST_W_TO_UB,
Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
Opcode.MSB -> throw CompilerException("msb of a byte")
Opcode.CAST_UB_TO_UW -> {
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_W -> {
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_UW -> {
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.UWORD))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_UB_TO_W -> {
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.WORD))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> throw CompilerException("invalid conversion following a byte")
Opcode.DISCARD_BYTE -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
Opcode.MKWORD -> {}
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
}
}
for(blk in blocks) {
instructionsToReplace.clear()
val typeConversionOpcodes = setOf(
Opcode.MSB,
Opcode.MKWORD,
Opcode.CAST_UB_TO_B,
Opcode.CAST_UB_TO_UW,
Opcode.CAST_UB_TO_W,
Opcode.CAST_UB_TO_F,
Opcode.CAST_B_TO_UB,
Opcode.CAST_B_TO_UW,
Opcode.CAST_B_TO_W,
Opcode.CAST_B_TO_F,
Opcode.CAST_UW_TO_UB,
Opcode.CAST_UW_TO_B,
Opcode.CAST_UW_TO_W,
Opcode.CAST_UW_TO_F,
Opcode.CAST_W_TO_UB,
Opcode.CAST_W_TO_B,
Opcode.CAST_W_TO_UW,
Opcode.CAST_W_TO_F,
Opcode.CAST_F_TO_UB,
Opcode.CAST_F_TO_B,
Opcode.CAST_F_TO_UW,
Opcode.CAST_F_TO_W,
Opcode.DISCARD_BYTE,
Opcode.DISCARD_WORD,
Opcode.DISCARD_FLOAT
)
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[1].value.opcode in typeConversionOpcodes) {
when (it[0].value.opcode) {
Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value)
Opcode.PUSH_VAR_FLOAT,
Opcode.PUSH_VAR_WORD,
Opcode.PUSH_VAR_BYTE,
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB,
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW,
Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value)
else -> {
}
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
fun variable(scopedname: String, decl: VarDecl) {
when(decl.type) {
VarDeclType.VAR -> {
// var decls that are defined inside of a StructDecl are skipped in the output
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
if(decl.parent is StructDecl)
return
val valueparams = VariableParameters(decl.zeropage, decl.struct)
val value = when(decl.datatype) {
in NumericDatatypes -> {
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
}
in StringDatatypes -> {
val litval = (decl.value as ReferenceLiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
in ArrayDatatypes -> {
val litval = (decl.value as? ReferenceLiteralValue)
if(litval!=null && litval.heapId==null)
throw CompilerException("array should already be in the heap")
if(litval!=null){
RuntimeValue(decl.datatype, heapId = litval.heapId)
} else {
throw CompilerException("initialization value expected")
}
}
DataType.STRUCT -> {
// struct variables have been flattened already
return
}
else -> throw CompilerException("weird datatype")
}
currentBlock.variables.add(Variable(scopedname, value, valueparams))
}
VarDeclType.MEMORY -> {
// note that constants are all folded away, but assembly code may still refer to them
val lv = decl.value as NumericLiteralValue
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
throw CompilerException("expected integer memory address $lv")
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
}
VarDeclType.CONST -> {
// note that constants are all folded away, but assembly code may still refer to them (if their integers)
// floating point constants are not generated at all!!
val lv = decl.value as NumericLiteralValue
if(lv.type in IntegerDatatypes)
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
}
}
}
fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) {
currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2))
}
fun label(labelname: String, asmProc: Boolean=false) {
val instr = LabelInstr(labelname, asmProc)
currentBlock.instructions.add(instr)
currentBlock.labels[labelname] = instr
}
fun line(position: Position) {
currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
}
fun removeLastInstruction() {
currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex)
}
fun memoryPointer(name: String, address: Int, datatype: DataType) {
currentBlock.memoryPointers[name] = Pair(address, datatype)
}
fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(name, address, force_output = "force_output" in options)
blocks.add(currentBlock)
}
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
writeMemory(out)
writeHeap(out)
for(blk in blocks) {
writeBlock(out, blk, embeddedLabels)
}
}
private fun writeHeap(out: PrintStream) {
out.println("%heap")
heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
when {
it.value.str!=null ->
out.println("\"${escape(it.value.str!!)}\"")
it.value.array!=null -> {
// this array can contain both normal integers, and pointer values
val arrayvalues = it.value.array!!.map { av ->
when {
av.integer!=null -> av.integer.toString()
av.addressOf!=null -> {
if(av.addressOf.scopedname==null)
throw CompilerException("AddressOf scopedname should have been set")
else
"&${av.addressOf.scopedname}"
}
else -> throw CompilerException("weird array value")
}
}
out.println(arrayvalues)
}
it.value.doubleArray!=null ->
out.println(it.value.doubleArray!!.toList())
else -> throw CompilerException("invalid heap entry $it")
}
}
out.println("%end_heap")
}
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables")
for (variable in blk.variables) {
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
throw CompilerException("zp conflict")
val valuestr = variable.value.toString()
val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}"
out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct")
}
out.println("%end_variables")
out.println("%memorypointers")
for (iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
}
out.println("%end_memorypointers")
out.println("%instructions")
val labels = blk.labels.entries.associateBy({ it.value }) { it.key }
for (instr in blk.instructions) {
if (!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
out.println("%end_block")
}
private fun writeMemory(out: PrintStream) {
out.println("%memory")
if (memory.isNotEmpty())
TODO("add support for writing/reading initial memory values")
out.println("%end_memory")
}
}

View File

@ -1,291 +0,0 @@
package compiler.intermediate
enum class Opcode {
// pushing values on the (evaluation) stack
PUSH_BYTE, // push byte value
PUSH_WORD, // push word value (or 'address' of string / array)
PUSH_FLOAT, // push float value
PUSH_MEM_B, // push byte value from memory to stack
PUSH_MEM_UB, // push unsigned byte value from memory to stack
PUSH_MEM_W, // push word value from memory to stack
PUSH_MEM_UW, // push unsigned word value from memory to stack
PUSH_MEM_FLOAT, // push float value from memory to stack
PUSH_MEMREAD, // push memory value from address that's on the stack
PUSH_VAR_BYTE, // push byte variable (ubyte, byte)
PUSH_VAR_WORD, // push word variable (uword, word)
PUSH_VAR_FLOAT, // push float variable
PUSH_REGAX_WORD, // push registers A/X as a 16-bit word
PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word
PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word
PUSH_ADDR_HEAPVAR, // push the address of the variable that's on the heap (string or array)
DUP_B, // duplicate the top byte on the stack
DUP_W, // duplicate the top word on the stack
// popping values off the (evaluation) stack, possibly storing them in another location
DISCARD_BYTE, // discard top byte value
DISCARD_WORD, // discard top word value
DISCARD_FLOAT, // discard top float value
POP_MEM_BYTE, // pop (u)byte value into destination memory address
POP_MEM_WORD, // pop (u)word value into destination memory address
POP_MEM_FLOAT, // pop float value into destination memory address
POP_MEMWRITE, // pop address and byte stack and write the byte to the memory address
POP_VAR_BYTE, // pop (u)byte value into variable
POP_VAR_WORD, // pop (u)word value into variable
POP_VAR_FLOAT, // pop float value into variable
POP_REGAX_WORD, // pop uword from stack into A/X registers
POP_REGAY_WORD, // pop uword from stack into A/Y registers
POP_REGXY_WORD, // pop uword from stack into X/Y registers
// numeric arithmetic
ADD_UB,
ADD_B,
ADD_UW,
ADD_W,
ADD_F,
SUB_UB,
SUB_B,
SUB_UW,
SUB_W,
SUB_F,
MUL_UB,
MUL_B,
MUL_UW,
MUL_W,
MUL_F,
IDIV_UB,
IDIV_B,
IDIV_UW,
IDIV_W,
DIV_F,
REMAINDER_UB, // signed remainder is undefined/unimplemented
REMAINDER_UW, // signed remainder is undefined/unimplemented
POW_F,
NEG_B,
NEG_W,
NEG_F,
ABS_B,
ABS_W,
ABS_F,
// bit shifts and bitwise arithmetic
SHIFTEDL_BYTE, // shifts stack value rather than in-place mem/var
SHIFTEDL_WORD, // shifts stack value rather than in-place mem/var
SHIFTEDR_UBYTE, // shifts stack value rather than in-place mem/var
SHIFTEDR_SBYTE, // shifts stack value rather than in-place mem/var
SHIFTEDR_UWORD, // shifts stack value rather than in-place mem/var
SHIFTEDR_SWORD, // shifts stack value rather than in-place mem/var
SHL_BYTE,
SHL_WORD,
SHL_MEM_BYTE,
SHL_MEM_WORD,
SHL_VAR_BYTE,
SHL_VAR_WORD,
SHR_UBYTE,
SHR_SBYTE,
SHR_UWORD,
SHR_SWORD,
SHR_MEM_UBYTE,
SHR_MEM_SBYTE,
SHR_MEM_UWORD,
SHR_MEM_SWORD,
SHR_VAR_UBYTE,
SHR_VAR_SBYTE,
SHR_VAR_UWORD,
SHR_VAR_SWORD,
ROL_BYTE,
ROL_WORD,
ROL_MEM_BYTE,
ROL_MEM_WORD,
ROL_VAR_BYTE,
ROL_VAR_WORD,
ROR_BYTE,
ROR_WORD,
ROR_MEM_BYTE,
ROR_MEM_WORD,
ROR_VAR_BYTE,
ROR_VAR_WORD,
ROL2_BYTE,
ROL2_WORD,
ROL2_MEM_BYTE,
ROL2_MEM_WORD,
ROL2_VAR_BYTE,
ROL2_VAR_WORD,
ROR2_BYTE,
ROR2_WORD,
ROR2_MEM_BYTE,
ROR2_MEM_WORD,
ROR2_VAR_BYTE,
ROR2_VAR_WORD,
BITAND_BYTE,
BITAND_WORD,
BITOR_BYTE,
BITOR_WORD,
BITXOR_BYTE,
BITXOR_WORD,
INV_BYTE,
INV_WORD,
// numeric type conversions
MSB, // note: lsb is equivalent to CAST_UW_TO_UB or CAST_W_TO_UB
MKWORD, // create a word from lsb + msb
CAST_UB_TO_B,
CAST_UB_TO_UW,
CAST_UB_TO_W,
CAST_UB_TO_F,
CAST_B_TO_UB,
CAST_B_TO_UW,
CAST_B_TO_W,
CAST_B_TO_F,
CAST_W_TO_UB,
CAST_W_TO_B,
CAST_W_TO_UW,
CAST_W_TO_F,
CAST_UW_TO_UB,
CAST_UW_TO_B,
CAST_UW_TO_W,
CAST_UW_TO_F,
CAST_F_TO_UB,
CAST_F_TO_B,
CAST_F_TO_UW,
CAST_F_TO_W,
// logical operations
AND_BYTE,
AND_WORD,
OR_BYTE,
OR_WORD,
XOR_BYTE,
XOR_WORD,
NOT_BYTE,
NOT_WORD,
// increment, decrement
INC_VAR_B,
INC_VAR_UB,
INC_VAR_W,
INC_VAR_UW,
INC_VAR_F,
DEC_VAR_B,
DEC_VAR_UB,
DEC_VAR_W,
DEC_VAR_UW,
DEC_VAR_F,
INC_MEMORY, // increment direct address
DEC_MEMORY, // decrement direct address
POP_INC_MEMORY, // increment address from stack
POP_DEC_MEMORY, // decrement address from address
// comparisons
LESS_B,
LESS_UB,
LESS_W,
LESS_UW,
LESS_F,
GREATER_B,
GREATER_UB,
GREATER_W,
GREATER_UW,
GREATER_F,
LESSEQ_B,
LESSEQ_UB,
LESSEQ_W,
LESSEQ_UW,
LESSEQ_F,
GREATEREQ_B,
GREATEREQ_UB,
GREATEREQ_W,
GREATEREQ_UW,
GREATEREQ_F,
EQUAL_BYTE,
EQUAL_WORD,
EQUAL_F,
NOTEQUAL_BYTE,
NOTEQUAL_WORD,
NOTEQUAL_F,
CMP_B, // sets processor status flags based on comparison, instead of pushing a result value
CMP_UB, // sets processor status flags based on comparison, instead of pushing a result value
CMP_W, // sets processor status flags based on comparison, instead of pushing a result value
CMP_UW, // sets processor status flags based on comparison, instead of pushing a result value
// array access and simple manipulations
READ_INDEXED_VAR_BYTE,
READ_INDEXED_VAR_WORD,
READ_INDEXED_VAR_FLOAT,
WRITE_INDEXED_VAR_BYTE,
WRITE_INDEXED_VAR_WORD,
WRITE_INDEXED_VAR_FLOAT,
INC_INDEXED_VAR_B,
INC_INDEXED_VAR_UB,
INC_INDEXED_VAR_W,
INC_INDEXED_VAR_UW,
INC_INDEXED_VAR_FLOAT,
DEC_INDEXED_VAR_B,
DEC_INDEXED_VAR_UB,
DEC_INDEXED_VAR_W,
DEC_INDEXED_VAR_UW,
DEC_INDEXED_VAR_FLOAT,
// branching, without consuming a value from the stack
JUMP,
BCS, // branch if carry set
BCC, // branch if carry clear
BZ, // branch if zero flag
BNZ, // branch if not zero flag
BNEG, // branch if negative flag
BPOS, // branch if not negative flag
BVS, // branch if overflow flag
BVC, // branch if not overflow flag
// branching, based on value on the stack (which is consumed)
JZ, // branch if value is zero (byte)
JNZ, // branch if value is not zero (byte)
JZW, // branch if value is zero (word)
JNZW, // branch if value is not zero (word)
// subroutines
CALL,
RETURN,
SYSCALL,
START_PROCDEF,
END_PROCDEF,
// misc
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag
CARRY_TO_A, // load var/register A with carry status bit
RSAVE, // save all internal registers and status flags
RSAVEX, // save just X (the evaluation stack pointer)
RRESTORE, // restore all internal registers and status flags
RRESTOREX, // restore just X (the evaluation stack pointer)
NOP, // do nothing
BREAKPOINT, // breakpoint
TERMINATE, // end the program
LINE, // track source file line number
INLINE_ASSEMBLY, // container to hold inline raw assembly code
INCLUDE_FILE // directive to include a file at this position in the memory of the program
}
val opcodesWithVarArgument = setOf(
Opcode.INC_VAR_B, Opcode.INC_VAR_W, Opcode.DEC_VAR_B, Opcode.DEC_VAR_W,
Opcode.INC_VAR_UB, Opcode.INC_VAR_UW, Opcode.DEC_VAR_UB, Opcode.DEC_VAR_UW,
Opcode.SHR_VAR_SBYTE, Opcode.SHR_VAR_UBYTE, Opcode.SHR_VAR_SWORD, Opcode.SHR_VAR_UWORD,
Opcode.SHL_VAR_BYTE, Opcode.SHL_VAR_WORD,
Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD,
Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD,
Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT,
Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR,
Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT,
Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT,
Opcode.INC_INDEXED_VAR_UB, Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UW,
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UW,
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_FLOAT
)
val branchOpcodes = setOf(
Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ,
Opcode.BNEG, Opcode.BPOS, Opcode.BVS, Opcode.BVC
)

View File

@ -1,761 +0,0 @@
package compiler.target.c64.codegen
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.statements.ZeropageWish
import prog8.compiler.*
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.File
import java.util.*
import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg)
internal fun intVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue()
internal fun hexVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue().toHex()
internal fun hexValPlusOne(valueInstr: Instruction) = (valueInstr.arg!!.integerValue()+1).toHex()
internal fun getFloatConst(value: RuntimeValue): String =
globalFloatConsts[value.numericValue().toDouble()]
?: throw AssemblyError("should have a global float const for number $value")
internal val globalFloatConsts = mutableMapOf<Double, String>()
internal fun signExtendA(into: String) =
"""
ora #$7f
bmi +
lda #0
+ sta $into
"""
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
private val heap: HeapValues, private val zeropage: Zeropage) {
private val assemblyLines = mutableListOf<String>()
private lateinit var block: IntermediateProgram.ProgramBlock
init {
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map {
when {
it is LabelInstr -> LabelInstr(symname(it.name, block), it.asmProc)
it.opcode == Opcode.INLINE_ASSEMBLY -> it
else ->
Instruction(it.opcode, it.arg, it.arg2,
callLabel = if (it.callLabel != null) symname(it.callLabel, block) else null,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
}
}.toMutableList()
val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock(
block.name,
block.address,
newinstructions,
newvars,
newMempointers,
newlabels,
force_output = block.force_output)
newblocks.add(newblock)
}
program.blocks.clear()
program.blocks.addAll(newblocks)
val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value}
program.allocatedZeropageVariables.clear()
program.allocatedZeropageVariables.putAll(newAllocatedZp)
// make a list of all const floats that are used
for(block in program.blocks) {
for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) {
val float = ins.arg!!.numericValue().toDouble()
if(float !in globalFloatConsts)
globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}"
}
}
}
fun compileToAssembly(optimize: Boolean): AssemblyProgram {
println("Generating assembly code from intermediate code... ")
assemblyLines.clear()
header()
for(b in program.blocks)
block2asm(b)
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
File("${program.name}.asm").printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name)
}
private fun out(str: String, splitlines: Boolean=true) {
if(splitlines) {
for (line in str.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
} else assemblyLines.add(str)
}
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped)
return scoped
val blockLocal: Boolean
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
blockLocal = true
scoped.substring(block.name.length+1)
}
else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-")
return "-"
if(blockLocal)
name = name.replace(".", "_")
else {
val parts = name.split(".", limit=2)
if(parts.size>1)
name = "${parts[0]}.${parts[1].replace(".", "_")}"
}
return name.replace("-", "")
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
val b3 = "$"+flt.b3.toString(16).padStart(2, '0')
val b4 = "$"+flt.b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${Date()}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
if(program.loadAddress==0) // fix load address
program.loadAddress = if(options.launcher==LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
if (program.loadAddress != 0x0801)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.loadAddress.toHex()}")
val year = Calendar.getInstance().get(Calendar.YEAR)
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.loadAddress.toHex()}\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
out("* = ${program.loadAddress.toHex()}\n")
}
}
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr
if(initVarsLabel!=null)
out(" jsr ${block.name}.${initVarsLabel.name}")
}
out(" clc")
when(zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
// the global list of all floating point constants for the whole program
for(flt in globalFloatConsts) {
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
}
}
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk
out("\n\n; ---- block: '${block.name}' ----")
if(!blk.force_output)
out("${block.name}\t.proc\n")
if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}")
}
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.scopedname, null)
val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.value.type in zeropage.allowedDatatypes
&& variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
else {
// it was already allocated on the zp
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
out("\n; memdefs and kernel subroutines")
memdefs2asm(block)
out("\n; non-zeropage variables")
vardecls2asm(block)
out("")
val instructionPatternWindowSize = 8 // increase once patterns occur longer than this.
var processed = 0
for (ins in block.instructions.windowed(instructionPatternWindowSize, partialWindows = true)) {
if (processed == 0) {
processed = instr2asm(ins)
if (processed == 0) {
// the instructions are not recognised yet and can't be translated into assembly
throw CompilerException("no asm translation found for instruction pattern: $ins")
}
}
processed--
}
if(!blk.force_output)
out("\n\t.pend\n")
}
private fun memdefs2asm(block: IntermediateProgram.ProgramBlock) {
for(m in block.memoryPointers) {
out(" ${m.key} = ${m.value.first.toHex()}")
}
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
val uniqueNames = block.variables.map { it.scopedname }.toSet()
if (uniqueNames.size != block.variables.size)
throw AssemblyError("not all variables have unique names")
// these are the non-zeropage variables.
// first get all the flattened struct members, they MUST remain in order
out("; flattened struct members")
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
// sort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.value.type }
for (variable in sortedVars) {
val sym = symname(block.name + "." + variable.scopedname, null)
if(sym in program.allocatedZeropageVariables)
continue // skip the ones that already belong in the zero page
vardecl2asm(variable.scopedname, variable.value, variable.params)
}
}
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
when (value.type) {
DataType.UBYTE -> out("$varname\t.byte 0")
DataType.BYTE -> out("$varname\t.char 0")
DataType.UWORD -> out("$varname\t.word 0")
DataType.WORD -> out("$varname\t.sint 0")
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(value.heapId!!).str!!
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.byte ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
// signed integer byte arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.char ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.word ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
// signed word arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.sint ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(varname)
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
}
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
}
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
return when {
value.type== DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type== DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
else -> throw AssemblyError("weird type in array")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
// note: array of signed value can never contain pointer-to type, so simply accept values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map {
if(it.integer!!>=0)
"$"+it.integer.toString(16).padStart(2, '0')
else
"-$"+abs(it.integer).toString(16).padStart(2, '0')
}
}
else throw AssemblyError("invalid arraysize type")
}
private fun instr2asm(ins: List<Instruction>): Int {
// find best patterns (matching the most of the lines, then with the smallest weight)
val fragments = findPatterns(ins).sortedByDescending { it.segmentSize }
if(fragments.isEmpty()) {
// we didn't find any matching patterns (complex multi-instruction fragments), try simple ones
val firstIns = ins[0]
val singleAsm = simpleInstr2Asm(firstIns, block)
if(singleAsm != null) {
outputAsmFragment(singleAsm)
return 1
}
return 0
}
val best = fragments[0]
outputAsmFragment(best.asm)
return best.segmentSize
}
private fun outputAsmFragment(singleAsm: String) {
if (singleAsm.isNotEmpty()) {
if(singleAsm.startsWith("@inline@"))
out(singleAsm.substring(8), false)
else {
val withNewlines = singleAsm.replace('|', '\n')
out(withNewlines)
}
}
}
private fun findPatterns(segment: List<Instruction>): List<AsmFragment> {
val opcodes = segment.map { it.opcode }
val result = mutableListOf<AsmFragment>()
// check for operations that modify a single value, by putting it on the stack (and popping it afterwards)
if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[2]==Opcode.POP_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_WORD && opcodes[2]==Opcode.POP_VAR_WORD) ||
(opcodes[0]==Opcode.PUSH_VAR_FLOAT && opcodes[2]==Opcode.POP_VAR_FLOAT)) {
if (segment[0].callLabel == segment[2].callLabel) {
val fragment = sameVarOperation(segment[0].callLabel!!, segment[1])
if (fragment != null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_MEM_UB && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_B && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_UW && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_W && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_FLOAT && opcodes[2]==Opcode.POP_MEM_FLOAT)) {
if(segment[0].arg==segment[2].arg) {
val fragment = sameMemOperation(segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].arg==segment[3].arg && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].callLabel==segment[3].callLabel && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
// add any matching patterns from the big list
for(pattern in Patterns.patterns) {
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
continue // don't accept patterns that don't fit
val opcodesList = opcodes.subList(0, pattern.sequence.size)
if(pattern.sequence == opcodesList) {
val asm = pattern.asm(segment)
if(asm!=null)
result.add(AsmFragment(asm, pattern.sequence.size))
} else if(pattern.altSequence!=null) {
val opcodesListAlt = opcodes.subList(0, pattern.altSequence.size)
if(pattern.altSequence == opcodesListAlt) {
val asm = pattern.asm(segment)
if (asm != null)
result.add(AsmFragment(asm, pattern.sequence.size))
}
}
}
return result
}
private fun sameConstantIndexedVarOperation(variable: String, index: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-value / pop-into-indexed-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f
""")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f
""")
else -> null
}
}
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
val loadXWord: String
val loadX: String
when(indexVar) {
"X" -> {
loadX = ""
loadXWord = " txa | asl a | tax |"
}
"Y" -> {
loadX = " tya | tax |"
loadXWord = " tya | asl a | tax |"
}
"A" -> {
loadX = " tax |"
loadXWord = " asl a | tax |"
}
else -> {
// the indexvar is a real variable, not a register
loadX = " ldx $indexVar |"
loadXWord = " lda $indexVar | asl a | tax |"
}
}
return when (ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" txa | $loadX asl $variable,x | tax", 10)
Opcode.SHR_UBYTE -> AsmFragment(" txa | $loadX lsr $variable,x | tax", 10)
Opcode.SHR_SBYTE -> AsmFragment("$saveX $loadX lda $variable,x | asl a | ror $variable,x $restoreX", 10)
Opcode.SHL_WORD -> AsmFragment("$saveX $loadXWord asl $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.SHR_UWORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.SHR_SWORD -> AsmFragment("$saveX $loadXWord lda $variable+1,x | asl a | ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL_BYTE -> AsmFragment(" txa | $loadX rol $variable,x | tax", 10)
Opcode.ROR_BYTE -> AsmFragment(" txa | $loadX ror $variable,x | tax", 10)
Opcode.ROL_WORD -> AsmFragment("$saveX $loadXWord rol $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.ROR_WORD -> AsmFragment("$saveX $loadXWord ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | cmp #\$80 | rol $variable,x $restoreX", 10)
Opcode.ROR2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | lsr a | bcc + | ora #\$80 |+ | sta $variable,x $restoreX", 10)
Opcode.ROL2_WORD -> AsmFragment(" txa | $loadXWord asl $variable,x | rol $variable+1,x | bcc + | inc $variable,x |+ | tax", 30)
Opcode.ROR2_WORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x | bcc + | lda $variable+1,x | ora #\$80 | sta $variable+1,x |+ $restoreX", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX inc $variable,x | tax", 10)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX dec $variable,x | tax", 10)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord inc $variable,x | bne + | inc $variable+1,x |+ $restoreX", 10)
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord lda $variable,x | bne + | dec $variable+1,x |+ | dec $variable,x $restoreX", 10)
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.inc_indexed_var_f $restoreX")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.dec_indexed_var_f $restoreX")
else -> null
}
}
private fun sameMemOperation(address: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of push-mem / op / pop-mem
val addr = address.toHex()
val addrHi = (address+1).toHex()
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $addr", 10)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $addr", 10)
Opcode.SHR_SBYTE -> AsmFragment(" lda $addr | asl a | ror $addr", 10)
Opcode.SHL_WORD -> AsmFragment(" asl $addr | rol $addrHi", 10)
Opcode.SHR_UWORD -> AsmFragment(" lsr $addrHi | ror $addr", 10)
Opcode.SHR_SWORD -> AsmFragment(" lda $addrHi | asl a | ror $addrHi | ror $addr", 10)
Opcode.ROL_BYTE -> AsmFragment(" rol $addr", 10)
Opcode.ROR_BYTE -> AsmFragment(" ror $addr", 10)
Opcode.ROL_WORD -> AsmFragment(" rol $addr | rol $addrHi", 10)
Opcode.ROR_WORD -> AsmFragment(" ror $addrHi | ror $addr", 10)
Opcode.ROL2_BYTE -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr", 10)
Opcode.ROR2_BYTE -> AsmFragment(" lda $addr | lsr a | bcc + | ora #\$80 |+ | sta $addr", 10)
Opcode.ROL2_WORD -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr | rol $addrHi", 10)
Opcode.ROR2_WORD -> AsmFragment(" lsr $addrHi | ror $addr | bcc + | lda $addrHi | ora #$80 | sta $addrHi |+", 20)
else -> null
}
}
private fun sameVarOperation(variable: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-var / op / pop-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" asl a", 10)
"X" -> AsmFragment(" txa | asl a | tax", 10)
"Y" -> AsmFragment(" tya | asl a | tay", 10)
else -> AsmFragment(" asl $variable", 10)
}
}
Opcode.SHR_UBYTE -> {
when (variable) {
"A" -> AsmFragment(" lsr a", 10)
"X" -> AsmFragment(" txa | lsr a | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | tay", 10)
else -> AsmFragment(" lsr $variable", 10)
}
}
Opcode.SHR_SBYTE -> {
// arithmetic shift right (keep sign bit)
when (variable) {
"A" -> AsmFragment(" cmp #$80 | ror a", 10)
"X" -> AsmFragment(" txa | cmp #$80 | ror a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #$80 | ror a | tay", 10)
else -> AsmFragment(" lda $variable | asl a | ror $variable", 10)
}
}
Opcode.SHL_WORD -> {
AsmFragment(" asl $variable | rol $variable+1", 10)
}
Opcode.SHR_UWORD -> {
AsmFragment(" lsr $variable+1 | ror $variable", 10)
}
Opcode.SHR_SWORD -> {
// arithmetic shift right (keep sign bit)
AsmFragment(" lda $variable+1 | asl a | ror $variable+1 | ror $variable", 10)
}
Opcode.ROL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" rol a", 10)
"X" -> AsmFragment(" txa | rol a | tax", 10)
"Y" -> AsmFragment(" tya | rol a | tay", 10)
else -> AsmFragment(" rol $variable", 10)
}
}
Opcode.ROR_BYTE -> {
when (variable) {
"A" -> AsmFragment(" ror a", 10)
"X" -> AsmFragment(" txa | ror a | tax", 10)
"Y" -> AsmFragment(" tya | ror a | tay", 10)
else -> AsmFragment(" ror $variable", 10)
}
}
Opcode.ROL_WORD -> {
AsmFragment(" rol $variable | rol $variable+1", 10)
}
Opcode.ROR_WORD -> {
AsmFragment(" ror $variable+1 | ror $variable", 10)
}
Opcode.ROL2_BYTE -> { // 8-bit rol
when (variable) {
"A" -> AsmFragment(" cmp #\$80 | rol a", 10)
"X" -> AsmFragment(" txa | cmp #\$80 | rol a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #\$80 | rol a | tay", 10)
else -> AsmFragment(" lda $variable | cmp #\$80 | rol $variable", 10)
}
}
Opcode.ROR2_BYTE -> { // 8-bit ror
when (variable) {
"A" -> AsmFragment(" lsr a | bcc + | ora #\$80 |+", 10)
"X" -> AsmFragment(" txa | lsr a | bcc + | ora #\$80 |+ | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | bcc + | ora #\$80 |+ | tay", 10)
else -> AsmFragment(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable", 10)
}
}
Opcode.ROL2_WORD -> {
AsmFragment(" lda $variable | cmp #\$80 | rol $variable | rol $variable+1", 10)
}
Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
}
else -> null
}
}
private class AsmFragment(val asm: String, var segmentSize: Int=0)
}

View File

@ -1,559 +0,0 @@
package compiler.target.c64.codegen
import prog8.compiler.CompilerException
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
// a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) {
val labelresult =
if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.name.length+1)
else
ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
}
// simple opcodes that are translated directly into one or a few asm instructions
return when(ins.opcode) {
Opcode.LINE -> " ;\tsrc line: ${ins.callLabel}"
Opcode.NOP -> " nop" // shouldn't be present anymore though
Opcode.START_PROCDEF -> "" // is done as part of a label
Opcode.END_PROCDEF -> " .pend"
Opcode.TERMINATE -> " brk"
Opcode.SEC -> " sec"
Opcode.CLC -> " clc"
Opcode.SEI -> " sei"
Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> {
if(ins.callLabel!=null)
" jmp ${ins.callLabel}"
else
" jmp ${hexVal(ins)}"
}
Opcode.CALL -> {
if(ins.callLabel!=null)
" jsr ${ins.callLabel}"
else
" jsr ${hexVal(ins)}"
}
Opcode.RETURN -> " rts"
Opcode.RSAVE -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}"
}
Opcode.RRESTORE -> {
// restore all registers and cpu status flag
" pla | tay | pla | tax | pla | plp"
}
Opcode.RSAVEX -> " sta ${C64Zeropage.SCRATCH_REG} | txa | pha | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.DUP_B -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
}
Opcode.DUP_W -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda $ESTACK_HI_HEX,x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_HEX,x
cmp #<${ins.arg.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
}
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
" .binary \"${ins.callLabel}\" $offset $length"
}
Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
val call = Syscall.values().find { it.callNr==ins.arg.numericValue() }
when(call) {
Syscall.FUNC_SIN,
Syscall.FUNC_COS,
Syscall.FUNC_ABS,
Syscall.FUNC_TAN,
Syscall.FUNC_ATAN,
Syscall.FUNC_LN,
Syscall.FUNC_LOG2,
Syscall.FUNC_SQRT,
Syscall.FUNC_RAD,
Syscall.FUNC_DEG,
Syscall.FUNC_ROUND,
Syscall.FUNC_FLOOR,
Syscall.FUNC_CEIL,
Syscall.FUNC_RNDF,
Syscall.FUNC_ANY_F,
Syscall.FUNC_ALL_F,
Syscall.FUNC_MAX_F,
Syscall.FUNC_MIN_F,
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
null -> ""
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
}
}
Opcode.BREAKPOINT -> {
breakpointCounter++
"_prog8_breakpoint_$breakpointCounter\tnop"
}
Opcode.PUSH_BYTE -> {
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
}
Opcode.PUSH_WORD -> {
val value = hexVal(ins)
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_FLOAT -> {
val floatConst = getFloatConst(ins.arg!!)
" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float"
}
Opcode.PUSH_VAR_BYTE -> {
when(ins.callLabel) {
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
"A" -> " sta $ESTACK_LO_HEX,x | dex"
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
}
}
Opcode.PUSH_VAR_WORD -> {
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
dex
"""
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(ins)}
sta $ESTACK_HI_HEX,x
dex
"""
}
Opcode.PUSH_MEM_FLOAT -> {
" lda #<${hexVal(ins)} | ldy #>${hexVal(ins)}| jsr c64flt.push_float"
}
Opcode.PUSH_MEMREAD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
sta (+) +1
lda $ESTACK_HI_PLUS1_HEX,x
sta (+) +2
+ lda 65535 ; modified
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.PUSH_REGAY_WORD -> {
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
}
Opcode.READ_INDEXED_VAR_BYTE -> {
"""
ldy $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_WORD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
asl a
tay
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel}+1,y
sta $ESTACK_HI_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.push_float_from_indexed_var
"""
}
Opcode.WRITE_INDEXED_VAR_BYTE -> {
"""
inx
ldy $ESTACK_LO_HEX,x
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
"""
}
Opcode.WRITE_INDEXED_VAR_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
asl a
tay
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
lda $ESTACK_HI_HEX,x
sta ${ins.callLabel}+1,y
"""
}
Opcode.WRITE_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.pop_float_to_indexed_var
"""
}
Opcode.POP_MEM_BYTE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
"""
}
Opcode.POP_MEM_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
lda $ESTACK_HI_HEX,x
sta ${hexValPlusOne(ins)}
"""
}
Opcode.POP_MEM_FLOAT -> {
" lda ${hexVal(ins)} | ldy ${hexValPlusOne(ins)} | jsr c64flt.pop_float"
}
Opcode.POP_MEMWRITE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
inx
lda $ESTACK_LO_HEX,x
+ sta 65535 ; modified
"""
}
Opcode.POP_VAR_BYTE -> {
when (ins.callLabel) {
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
"A" -> " inx | lda $ESTACK_LO_HEX,x"
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
}
}
Opcode.POP_VAR_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
}
Opcode.POP_VAR_FLOAT -> {
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
}
Opcode.INC_VAR_UB, Opcode.INC_VAR_B -> {
when (ins.callLabel) {
"A" -> " clc | adc #1"
"X" -> " inx"
"Y" -> " iny"
else -> " inc ${ins.callLabel}"
}
}
Opcode.INC_VAR_UW, Opcode.INC_VAR_W -> {
" inc ${ins.callLabel} | bne + | inc ${ins.callLabel}+1 |+"
}
Opcode.INC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.inc_var_f
"""
}
Opcode.POP_INC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ inc 65535 ; modified
"""
}
Opcode.POP_DEC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ dec 65535 ; modified
"""
}
Opcode.DEC_VAR_UB, Opcode.DEC_VAR_B -> {
when (ins.callLabel) {
"A" -> " sec | sbc #1"
"X" -> " dex"
"Y" -> " dey"
else -> " dec ${ins.callLabel}"
}
}
Opcode.DEC_VAR_UW, Opcode.DEC_VAR_W -> {
" lda ${ins.callLabel} | bne + | dec ${ins.callLabel}+1 |+ | dec ${ins.callLabel}"
}
Opcode.DEC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.dec_var_f
"""
}
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
Opcode.NEG_F -> " jsr c64flt.neg_f"
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
Opcode.ABS_F -> " jsr c64flt.abs_f"
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
Opcode.NOT_BYTE -> " jsr prog8_lib.not_byte"
Opcode.NOT_WORD -> " jsr prog8_lib.not_word"
Opcode.BCS -> {
val label = ins.callLabel ?: hexVal(ins)
" bcs $label"
}
Opcode.BCC -> {
val label = ins.callLabel ?: hexVal(ins)
" bcc $label"
}
Opcode.BNEG -> {
val label = ins.callLabel ?: hexVal(ins)
" bmi $label"
}
Opcode.BPOS -> {
val label = ins.callLabel ?: hexVal(ins)
" bpl $label"
}
Opcode.BVC -> {
val label = ins.callLabel ?: hexVal(ins)
" bvc $label"
}
Opcode.BVS -> {
val label = ins.callLabel ?: hexVal(ins)
" bvs $label"
}
Opcode.BZ -> {
val label = ins.callLabel ?: hexVal(ins)
" beq $label"
}
Opcode.BNZ -> {
val label = ins.callLabel ?: hexVal(ins)
" bne $label"
}
Opcode.JZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
"""
}
Opcode.JZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
lda $ESTACK_HI_HEX,x
beq $label
"""
}
Opcode.JNZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
"""
}
Opcode.JNZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
lda $ESTACK_HI_HEX,x
bne $label
"""
}
Opcode.CAST_B_TO_UB -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_UB_TO_B -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_W_TO_UW -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_UW_TO_W -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_W_TO_UB -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_W_TO_B -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_UW_TO_UB -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UW_TO_B -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UB_TO_F -> " jsr c64flt.stack_ub2float"
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
Opcode.SUB_W, Opcode.SUB_UW -> " jsr prog8_lib.sub_w"
Opcode.MUL_B, Opcode.MUL_UB -> " jsr prog8_lib.mul_byte"
Opcode.MUL_W, Opcode.MUL_UW -> " jsr prog8_lib.mul_word"
Opcode.MUL_F -> " jsr c64flt.mul_f"
Opcode.ADD_F -> " jsr c64flt.add_f"
Opcode.SUB_F -> " jsr c64flt.sub_f"
Opcode.DIV_F -> " jsr c64flt.div_f"
Opcode.IDIV_UB -> " jsr prog8_lib.idiv_ub"
Opcode.IDIV_B -> " jsr prog8_lib.idiv_b"
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
Opcode.GREATER_F -> " jsr c64flt.greater_f"
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
Opcode.LESS_B -> " jsr prog8_lib.less_b"
Opcode.LESS_UW -> " jsr prog8_lib.less_uw"
Opcode.LESS_W -> " jsr prog8_lib.less_w"
Opcode.LESS_F -> " jsr c64flt.less_f"
Opcode.LESSEQ_UB -> " jsr prog8_lib.lesseq_ub"
Opcode.LESSEQ_B -> " jsr prog8_lib.lesseq_b"
Opcode.LESSEQ_UW -> " jsr prog8_lib.lesseq_uw"
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
else -> null
}
}

View File

@ -1,47 +0,0 @@
package prog8.vm.stackvm
import prog8.printSoftwareHeader
import prog8.vm.astvm.ScreenDialog
import java.awt.EventQueue
import javax.swing.Timer
import kotlin.system.exitProcess
fun main(args: Array<String>) {
stackVmMain(args)
}
fun stackVmMain(args: Array<String>) {
printSoftwareHeader("StackVM")
if(args.size != 1) {
System.err.println("requires one argument: name of stackvm sourcecode file")
exitProcess(1)
}
val program = Program.load(args.first())
val vm = StackVm(traceOutputFile = null)
val dialog = ScreenDialog("StackVM")
vm.load(program, dialog.canvas)
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
val programTimer = Timer(10) { a ->
try {
vm.step()
} catch(bp: VmBreakpointException) {
println("Breakpoint: execution halted. Press enter to resume.")
readLine()
} catch (tx: VmTerminationException) {
println("Execution halted: ${tx.message}")
(a.source as Timer).stop()
}
}
val irqTimer = Timer(1000/60) { a -> vm.irq(a.`when`) }
programTimer.start()
irqTimer.start()
}
}

View File

@ -1,302 +0,0 @@
package prog8.vm.stackvm
import prog8.ast.antlr.unescape
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.opcodesWithVarArgument
import prog8.vm.RuntimeValue
import java.io.File
import java.util.*
import java.util.regex.Pattern
class Program (val name: String,
val program: MutableList<Instruction>,
val variables: Map<String, RuntimeValue>,
val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Int>,
val memory: Map<Int, List<RuntimeValue>>,
val heap: HeapValues)
{
init {
// add end of program marker and some sentinel instructions, to correctly connect all others
program.add(LabelInstr("____program_end", false))
program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP))
}
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
val heap = HeapValues()
val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, RuntimeValue>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Int>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
continue
else if(line=="%memory")
loadMemory(lines, memory)
else if(line=="%heap")
loadHeap(lines, heap)
else if(line.startsWith("%block "))
loadBlock(lines, heap, program, variables, memoryPointers, labels)
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
return Program(filename, program, variables, memoryPointers, labels, memory, heap)
}
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
heap: HeapValues,
program: MutableList<Instruction>,
variables: MutableMap<String, RuntimeValue>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Int>)
{
while(true) {
val (_, line) = lines.next()
if(line.isEmpty())
continue
else if(line=="%end_block")
return
else if(line=="%variables")
loadVars(lines, variables)
else if(line=="%memorypointers")
loadMemoryPointers(lines, memoryPointers, heap)
else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
val baseIndex = program.size
program.addAll(blockInstructions)
val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
labels.putAll(labelsWithIndex)
}
}
}
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
val splitpattern = Pattern.compile("\\s+")
val heapvalues = mutableListOf<Triple<Int, DataType, String>>()
while(true) {
val (_, line) = lines.next()
if (line == "%end_heap")
break
val parts = line.split(splitpattern, limit=3)
val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2])
heapvalues.add(value)
}
heapvalues.sortedBy { it.first }.forEach {
when(it.second) {
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0)))
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val intarray = numbers.map{number->
val num=number.trim()
if(num.startsWith("&")) {
// it's AddressOf
val scopedname = num.substring(1)
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0))
addrOf.scopedname=scopedname
IntegerOrAddressOf(null, addrOf)
} else {
IntegerOrAddressOf(num.toInt(), null)
}
}.toTypedArray()
heap.addIntegerArray(it.second, intarray)
}
DataType.ARRAY_F -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
heap.addDoublesArray(doublearray)
}
in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
else -> throw VmExecutionException("weird datatype")
}
}
}
private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> {
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
val nextInstructionLabels = Stack<String>() // more than one label can occur on the isSameAs line
while(true) {
val (lineNr, line) = lines.next()
if(line.isEmpty())
continue
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabels.push(line.substring(0, line.length-1))
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcodeStr = parts[0].toUpperCase()
val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
val args = if(parts.size==2) parts[1] else null
val instruction = when(opcode) {
Opcode.LINE -> Instruction(opcode, null, callLabel = args)
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(args!!.startsWith('$')) {
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
}
in opcodesWithVarArgument -> {
val withoutQuotes =
if(args!!.startsWith('"') && args.endsWith('"'))
args.substring(1, args.length-1) else args
Instruction(opcode, callLabel = withoutQuotes)
}
Opcode.SYSCALL -> {
if(args!! in syscallNames) {
val call = Syscall.valueOf(args)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
}
Opcode.INCLUDE_FILE -> {
val argparts = args!!.split(' ')
val filename = argparts[0]
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
Instruction(opcode, offset, length, filename)
}
else -> {
Instruction(opcode, getArgValue(args, heap))
}
}
instructions.add(instruction)
while(nextInstructionLabels.isNotEmpty()) {
val label = nextInstructionLabels.pop()
labels[label] = instruction
}
} else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
}
private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap")
}
val (type, valueStr) = args.split(':')
return when(type) {
"b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
"ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
"w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
"uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
"f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
"heap" -> {
val heapId = valueStr.toInt()
RuntimeValue(heap.get(heapId).type, heapId = heapId)
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, RuntimeValue>) {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (_, line) = lines.next()
if(line=="%end_variables")
return
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character")
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
in StringDatatypes -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference")
else {
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
in ArrayDatatypes -> {
if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference")
else {
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
else -> throw VmExecutionException("weird datatype")
}
vars[name] = value
}
}
private fun loadMemoryPointers(lines: Iterator<IndexedValue<String>>,
pointers: MutableMap<String, Pair<Int, DataType>>,
heap: HeapValues) {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (_, line) = lines.next()
if(line=="%end_memorypointers")
return
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character")
val type = DataType.valueOf(typeStr.toUpperCase())
val value = getArgValue(valueStr, heap)!!.integerValue()
pointers[name] = Pair(value, type)
}
}
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> {
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_memory")
return memory
val address = line.substringBefore(' ').toInt(16)
val rest = line.substringAfter(' ').trim()
if(rest.startsWith('"')) {
TODO("memory init with char/string")
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<RuntimeValue>()
valueStrings.forEach {
when(it.length) {
2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1")
}
}
memory[address] = values
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
===========================================================================
@ -17,7 +18,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with a input- and output parameter signature
- subroutines with an input- and output parameter signature
- constant folding in expressions
- conditional branches
- 'when' statement to provide a concise jump table alternative to if/elseif chains
@ -30,21 +31,18 @@ which aims to provide many conveniences over raw assembly code (even when using
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (seconds)
- option to automatically run the program in the Vice emulator
- use a modern PC to do the work on
- very quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- virtual machine that can execute compiled code directy on the host system,
without having to actually convert it to assembly to run on a real 6502
It is mainly targeted at the Commodore-64 machine at this time.
Prog8 is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/manual
--------------------
See https://prog8.readthedocs.io/
https://prog8.readthedocs.io/
Required tools
--------------

View File

@ -1,48 +1,52 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.1.0'
id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java'
}
apply plugin: "kotlin"
apply plugin: "java"
targetCompatibility = 1.8
sourceCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
// implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
// runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime 'org.antlr:antlr4-runtime:4.7.2'
runtime project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
verbose = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
@ -82,8 +86,8 @@ artifacts {
shadowJar {
baseName = 'prog8compiler'
version = prog8version
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
// minimize()
}
@ -97,7 +101,7 @@ test {
// Show test results.
testLogging {
events "passed", "skipped", "failed"
events "skipped", "failed"
}
}

View File

@ -11,8 +11,9 @@
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
</component>
</module>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,798 @@
; --- low level floating point assembly routines for the C64
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVAYF
jmp push_fac1_as_result
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr FREADUY
jmp push_fac1_as_result
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
lda $65
sta c64.ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
sta c64.ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #4
inx
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr NEGOP
jmp push_fac1_as_result
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ABS
jmp push_fac1_as_result
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda c64.ESTACK_LO-3,x
cmp c64.ESTACK_LO,x
bne _equals_false
lda c64.ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x
bne _equals_false
lda c64.ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x
bne _equals_false
lda c64.ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x
bne _equals_false
lda c64.ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta c64.ESTACK_LO+1,x
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
_return_result sta c64.ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
beq +
lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
tay
lda #0
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
clc
adc c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
bcc +
iny
+ jmp copy_float
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
.pend
swap_floats .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (c64.SCRATCH_ZPWORD1),y
pha
lda (c64.SCRATCH_ZPWORD2),y
sta (c64.SCRATCH_ZPWORD1),y
pla
sta (c64.SCRATCH_ZPWORD2),y
dey
bpl -
rts
.pend

View File

@ -10,8 +10,8 @@
c64flt {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ----
@ -34,32 +34,33 @@ c64flt {
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; oddly enough, 0.0 isn't available in the kernel.
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
; checked functions below:
asmsub MOVFM (uword mflpt @ AY) clobbers(A,Y) = $bba2 ; load mflpt value from memory in A/Y into fac1
asmsub FREADMEM () clobbers(A,Y) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
asmsub CONUPK (uword mflpt @ AY) clobbers(A,Y) = $ba8c ; load mflpt value from memory in A/Y into fac2
asmsub FAREADMEM () clobbers(A,Y) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
asmsub MOVFA () clobbers(A,X) = $bbfc ; copy fac2 to fac1
asmsub MOVAF () clobbers(A,X) = $bc0c ; copy fac1 to fac2 (rounded)
asmsub MOVEF () clobbers(A,X) = $bc0f ; copy fac1 to fac2
asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub GETADR () clobbers(X) -> ubyte @ Y, ubyte @ A = $b7f7
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
asmsub QINT () clobbers(A,X,Y) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
@ -67,50 +68,49 @@ asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
asmsub GIVAYF (ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) = $b391
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
asmsub FREADUY (ubyte value @ Y) clobbers(A,X,Y) = $b3a2 ; 8 bit unsigned Y -> float in fac1
asmsub FREADSA (byte value @ A) clobbers(A,X,Y) = $bc3c ; 8 bit signed A -> float in fac1
asmsub FREADSTR (ubyte length @ A) clobbers(A,X,Y) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
asmsub FPRINTLN () clobbers(A,X,Y) = $aabc ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
asmsub FOUT () clobbers(X) -> uword @ AY = $bddd ; fac1 -> string, address returned in AY ($0100)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
asmsub FADDH () clobbers(A,X,Y) = $b849 ; fac1 += 0.5, for rounding- call this before INT
asmsub MUL10 () clobbers(A,X,Y) = $bae2 ; fac1 *= 10
asmsub DIV10 () clobbers(A,X,Y) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
asmsub FCOMP (uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
asmsub FADDT () clobbers(A,X,Y) = $b86a ; fac1 += fac2
asmsub FADD (uword mflpt @ AY) clobbers(A,X,Y) = $b867 ; fac1 += mflpt value from A/Y
asmsub FSUBT () clobbers(A,X,Y) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
asmsub FSUB (uword mflpt @ AY) clobbers(A,X,Y) = $b850 ; fac1 = mflpt from A/Y - fac1
asmsub FMULTT () clobbers(A,X,Y) = $ba2b ; fac1 *= fac2
asmsub FMULT (uword mflpt @ AY) clobbers(A,X,Y) = $ba28 ; fac1 *= mflpt value from A/Y
asmsub FDIVT () clobbers(A,X,Y) = $bb12 ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
asmsub FDIV (uword mflpt @ AY) clobbers(A,X,Y) = $bb0f ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
asmsub FPWRT () clobbers(A,X,Y) = $bf7b ; fac1 = fac2 ** fac1
asmsub FPWR (uword mflpt @ AY) clobbers(A,X,Y) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
asmsub NOTOP () clobbers(A,X,Y) = $aed4 ; fac1 = NOT(fac1)
asmsub INT () clobbers(A,X,Y) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
asmsub LOG () clobbers(A,X,Y) = $b9ea ; fac1 = LN(fac1) (natural log)
asmsub SGN () clobbers(A,X,Y) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
asmsub SIGN () -> ubyte @ A = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
asmsub ABS () = $bc58 ; fac1 = ABS(fac1)
asmsub SQR () clobbers(A,X,Y) = $bf71 ; fac1 = SQRT(fac1)
asmsub SQRA () clobbers(A,X,Y) = $bf74 ; fac1 = SQRT(fac2)
asmsub EXP () clobbers(A,X,Y) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
asmsub NEGOP () clobbers(A) = $bfb4 ; switch the sign of fac1
asmsub RND () clobbers(A,X,Y) = $e097 ; fac1 = RND(fac1) float random number generator
asmsub COS () clobbers(A,X,Y) = $e264 ; fac1 = COS(fac1)
asmsub SIN () clobbers(A,X,Y) = $e26b ; fac1 = SIN(fac1)
asmsub TAN () clobbers(A,X,Y) = $e2b4 ; fac1 = TAN(fac1)
asmsub ATN () clobbers(A,X,Y) = $e30e ; fac1 = ATN(fac1)
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub FREADS32 () clobbers(A,X,Y) {
asmsub FREADS32() clobbers(A,X,Y) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
%asm {{
lda $62
@ -210,8 +210,8 @@ sub print_fln (float value) {
; ---- prints the floating point value (with a newline at the end) using basic rom routines
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<print_fln_value
ldy #>print_fln_value
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX
@ -220,750 +220,6 @@ sub print_fln (float value) {
}
; --- low level floating point assembly routines
%asm {{
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVAYF
jmp push_fac1_as_result
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr FREADUY
jmp push_fac1_as_result
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
lda $65
sta c64.ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
sta c64.ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #4
inx
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr NEGOP
jmp push_fac1_as_result
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ABS
jmp push_fac1_as_result
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda c64.ESTACK_LO-3,x
cmp c64.ESTACK_LO,x
bne _equals_false
lda c64.ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x
bne _equals_false
lda c64.ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x
bne _equals_false
lda c64.ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x
bne _equals_false
lda c64.ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta c64.ESTACK_LO+1,x
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
_return_result sta c64.ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
beq +
lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
}}
%asminclude "library:c64floats.asm", ""
} ; ------ end of block c64flt

View File

@ -7,178 +7,178 @@
c64 {
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&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
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
@ -186,8 +186,8 @@ c64 {
; ---- C64 basic routines ----
asmsub CLEARSCR () clobbers(A,X,Y) = $E544 ; clear the screen
asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
; ---- end of C64 basic routines ----
@ -195,48 +195,48 @@ asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
; ---- C64 kernal routines ----
asmsub STROUT (uword strptr @ AY) clobbers(A, X, Y) = $AB1E ; print null-terminated string (use c64scr.print instead)
asmsub IRQDFRT () clobbers(A,X,Y) = $EA31 ; default IRQ routine
asmsub IRQDFEND () clobbers(A,X,Y) = $EA81 ; default IRQ end/cleanup
asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
asmsub CIOUT (ubyte databyte @ A) = $FFA8 ; (alias: IECOUT) output byte to serial bus
asmsub UNTLK () clobbers(A) = $FFAB ; command serial bus device to UNTALK
asmsub UNLSN () clobbers(A) = $FFAE ; command serial bus device to UNLISTEN
asmsub LISTEN (ubyte device @ A) clobbers(A) = $FFB1 ; command serial bus device to LISTEN
asmsub TALK (ubyte device @ A) clobbers(A) = $FFB4 ; command serial bus device to TALK
asmsub READST () -> ubyte @ A = $FFB7 ; read I/O status word
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA ; set logical file parameters
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) = $FFBD ; set filename parameters
asmsub OPEN () clobbers(A,X,Y) = $FFC0 ; (via 794 ($31A)) open a logical file
asmsub CLOSE (ubyte logical @ A) clobbers(A,X,Y) = $FFC3 ; (via 796 ($31C)) close a logical file
asmsub CHKIN (ubyte logical @ X) clobbers(A,X) = $FFC6 ; (via 798 ($31E)) define an input channel
asmsub CHKOUT (ubyte logical @ X) clobbers(A,X) = $FFC9 ; (via 800 ($320)) define an output channel
asmsub CLRCHN () clobbers(A,X) = $FFCC ; (via 802 ($322)) restore default devices
asmsub CHRIN () clobbers(Y) -> ubyte @ A = $FFCF ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
asmsub CHROUT (ubyte char @ A) = $FFD2 ; (via 806 ($326)) output a character
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5 ; (via 816 ($330)) load from device
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8 ; (via 818 ($332)) save to a device
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) = $FFDB ; set the software clock
asmsub RDTIM () -> ubyte @ A, ubyte @ X, ubyte @ Y = $FFDE ; read the software clock
asmsub STOP () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc = $FFE1 ; (via 808 ($328)) check the STOP key
asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a character
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 kernal routines ----

View File

@ -15,28 +15,199 @@ c64utils {
const uword ESTACK_HI = $cf00
; ----- utility functions ----
; ----- number conversions to decimal strings
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
%asm {{
ldy #$2f
ldx #$3a
sec
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #$2f
rts
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
; ---- A (signed byte) to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
; note: the '-' is not part of the conversion here if it's a negative number
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = c64.SCRATCH_ZPB1 ; byte in zeropage
hexHigh = c64.SCRATCH_ZPWORD1 ; byte in zeropage
hexLow = c64.SCRATCH_ZPWORD1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
@ -48,7 +219,7 @@ asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
}
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
; ---- A to hex string in AY (first hex char in A, second hex char in Y)
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx c64.SCRATCH_ZPREGX
pha
@ -69,7 +240,6 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}}
}
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
@ -87,92 +257,6 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}}
}
asmsub uword2bcd (uword value @ AY) clobbers(A,Y) {
; Convert an 16 bit binary value to BCD
;
; This function converts a 16 bit binary value in A/Y into a 24 bit BCD. It
; works by transferring one bit a time from the source and adding it
; into a BCD value that is being doubled on each iteration. As all the
; arithmetic is being done in BCD the result is a binary to decimal
; conversion.
%asm {{
sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
php
pla ; read status register
and #%00000100
sta _had_irqd
sei ; disable interrupts because of bcd math
sed ; switch to decimal mode
lda #0 ; ensure the result is clear
sta bcdbuff+0
sta bcdbuff+1
sta bcdbuff+2
ldy #16 ; the number of source bits
- asl c64.SCRATCH_ZPB1 ; shift out one bit
rol c64.SCRATCH_ZPREG
lda bcdbuff+0 ; and add into result
adc bcdbuff+0
sta bcdbuff+0
lda bcdbuff+1 ; propagating any carry
adc bcdbuff+1
sta bcdbuff+1
lda bcdbuff+2 ; ... thru whole result
adc bcdbuff+2
sta bcdbuff+2
dey ; and repeat for next bit
bne -
cld ; back to binary
lda _had_irqd
bne +
cli ; enable interrupts again (only if they were enabled before)
+ rts
_had_irqd .byte 0
bcdbuff .byte 0,0,0
}}
}
asmsub uword2decimal (uword value @ AY) clobbers(A) -> ubyte @ Y {
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
; returns length of resulting string in Y
%asm {{
jsr uword2bcd
lda uword2bcd.bcdbuff+2
clc
adc #'0'
sta output
ldy #1
lda uword2bcd.bcdbuff+1
jsr +
lda uword2bcd.bcdbuff+0
+ pha
lsr a
lsr a
lsr a
lsr a
clc
adc #'0'
sta output,y
iny
pla
and #$0f
adc #'0'
sta output,y
iny
lda #0
sta output,y
rts
output .text "00000", $00 ; 0 terminated
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
@ -227,7 +311,6 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
@ -283,7 +366,6 @@ _negative .byte 0
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
@ -341,6 +423,7 @@ _irq_handler_init
dex
dex
dex
cld
rts
_irq_handler_end
@ -372,7 +455,6 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() {
%asm {{
sei
@ -389,7 +471,6 @@ asmsub restore_irqvec() {
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
@ -454,13 +535,11 @@ _raster_irq_handler
}
} ; ------ end of block c64utils
c64scr {
; ---- this block contains (character) Screen and text I/O related functions ----
@ -480,21 +559,15 @@ asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
ldy #0
_loop sta c64.Screen,y
sta c64.Screen+1,y
sta c64.Screen+$0100,y
sta c64.Screen+$0101,y
sta c64.Screen+$0200,y
sta c64.Screen+$0201,y
sta c64.Screen+$02e8,y
sta c64.Screen+$02e9,y
iny
iny
bne _loop
rts
@ -507,25 +580,20 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
%asm {{
ldy #0
_loop sta c64.Colors,y
sta c64.Colors+1,y
sta c64.Colors+$0100,y
sta c64.Colors+$0101,y
sta c64.Colors+$0200,y
sta c64.Colors+$0201,y
sta c64.Colors+$02e8,y
sta c64.Colors+$02e9,y
iny
iny
bne _loop
rts
}}
}
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx c64.SCRATCH_ZPREGX
bcs +
@ -535,18 +603,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
ldx #0
ldy #38
-
.for row=0, row<=12, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
inx
dey
bpl -
ldx #0
ldy #38
-
.for row=13, row<=24, row+=1
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
@ -558,18 +615,7 @@ _scroll_screen ; scroll the screen memory
ldx #0
ldy #38
-
.for row=0, row<=12, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
inx
dey
bpl -
ldx #0
ldy #38
-
.for row=13, row<=24, row+=1
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
@ -582,7 +628,6 @@ _scroll_screen ; scroll the screen memory
}}
}
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
@ -595,47 +640,28 @@ asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #38
-
.for row=0, row<=12, row+=1
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
ldx #38
-
.for row=13, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
_scroll_screen ; scroll the screen memory
ldx #38
-
.for row=0, row<=12, row+=1
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx #38
-
.for row=13, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx c64.SCRATCH_ZPREGX
rts
}}
}
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
@ -648,16 +674,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #39
-
.for row=1, row<=11, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex
bpl -
ldx #39
-
.for row=12, row<=24, row+=1
.for row=1, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
@ -667,16 +684,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=1, row<=11, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
ldx #39
-
.for row=12, row<=24, row+=1
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
@ -688,7 +696,6 @@ _scroll_screen ; scroll the screen memory
}}
}
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
@ -701,16 +708,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #39
-
.for row=23, row>=12, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
dex
bpl -
ldx #39
-
.for row=11, row>=0, row-=1
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
@ -720,16 +718,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=23, row>=12, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
ldx #39
-
.for row=11, row>=0, row-=1
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
@ -742,7 +731,6 @@ _scroll_screen ; scroll the screen memory
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
@ -761,7 +749,6 @@ asmsub print (str text @ AY) clobbers(A,Y) {
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
@ -770,16 +757,15 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
pha
tya
jsr c64.CHROUT
txa
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
@ -788,15 +774,17 @@ asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
_print_byte_digits
pha
cpy #'0'
bne _print_hundreds
cpx #'0'
bne _print_tens
jmp _end
_print_hundreds tya
beq +
tya
jsr c64.CHROUT
_print_tens txa
pla
jsr c64.CHROUT
_end pla
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
rts
@ -820,7 +808,6 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
@ -839,7 +826,6 @@ asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
@ -861,7 +847,6 @@ asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
@ -874,7 +859,6 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
@ -888,51 +872,45 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldy #0
- lda c64utils.uword2decimal.output,y
- lda c64utils.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
cpy #5
bne -
+ ldx c64.SCRATCH_ZPREGX
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldx c64.SCRATCH_ZPREGX
ldy #0
lda c64utils.uword2decimal.output
- lda c64utils.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _pr_decimal
iny
lda c64utils.uword2decimal.output+1
cmp #'0'
bne _pr_decimal
iny
lda c64utils.uword2decimal.output+2
cmp #'0'
bne _pr_decimal
iny
lda c64utils.uword2decimal.output+3
cmp #'0'
bne _pr_decimal
bne _gotdigit
iny
bne -
_pr_decimal
lda c64utils.uword2decimal.output,y
_gotdigit
jsr c64.CHROUT
iny
cpy #5
bcc _pr_decimal
lda c64utils.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}

View File

@ -239,7 +239,7 @@ mul_byte_3 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_3 .proc
; W*2 + W
lda c64.ESTACK_HI+1,x
@ -255,7 +255,7 @@ mul_word_3 .proc
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_5 .proc
; X*4 + X
@ -286,7 +286,7 @@ mul_word_5 .proc
rts
.pend
mul_byte_6 .proc
; (X*2 + X)*2
lda c64.ESTACK_LO+1,x
@ -327,7 +327,7 @@ mul_byte_7 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_7 .proc
; W*8 - W
lda c64.ESTACK_HI+1,x
@ -411,7 +411,7 @@ mul_word_10 .proc
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_11 .proc
; (X*2 + X)*4 - X
lda c64.ESTACK_LO+1,x
@ -488,7 +488,7 @@ mul_byte_14 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
; mul_word_14 is skipped (too much code)
mul_byte_15 .proc
@ -604,7 +604,7 @@ mul_word_25 .proc
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
.pend
mul_byte_40 .proc
; (X*4 + X)*8
@ -619,7 +619,7 @@ mul_byte_40 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_40 .proc
; (W*4 + W)*8
lda c64.ESTACK_HI+1,x
@ -680,3 +680,192 @@ _sign_possibly_zero lda c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
; bit shifts.
; anything below 3 is done inline. anything above 7 is done via other optimizations.
shift_left_w_7 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPB1
_shift6 asl a
rol c64.SCRATCH_ZPB1
_shift5 asl a
rol c64.SCRATCH_ZPB1
_shift4 asl a
rol c64.SCRATCH_ZPB1
_shift3 asl a
rol c64.SCRATCH_ZPB1
asl a
rol c64.SCRATCH_ZPB1
asl a
rol c64.SCRATCH_ZPB1
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPB1
sta c64.ESTACK_HI+1,x
rts
.pend
shift_left_w_6 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift6
.pend
shift_left_w_5 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift5
.pend
shift_left_w_4 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift4
.pend
shift_left_w_3 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift3
.pend
shift_right_uw_7 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
lsr a
ror c64.SCRATCH_ZPB1
_shift6 lsr a
ror c64.SCRATCH_ZPB1
_shift5 lsr a
ror c64.SCRATCH_ZPB1
_shift4 lsr a
ror c64.SCRATCH_ZPB1
_shift3 lsr a
ror c64.SCRATCH_ZPB1
lsr a
ror c64.SCRATCH_ZPB1
lsr a
ror c64.SCRATCH_ZPB1
sta c64.ESTACK_HI+1,x
lda c64.SCRATCH_ZPB1
sta c64.ESTACK_LO+1,x
rts
.pend
shift_right_uw_6 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift6
.pend
shift_right_uw_5 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift5
.pend
shift_right_uw_4 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift4
.pend
shift_right_uw_3 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift3
.pend
shift_right_w_7 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift6 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift5 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift4 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift3 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
sta c64.ESTACK_HI+1,x
rts
.pend
shift_right_w_6 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift6
.pend
shift_right_w_5 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift5
.pend
shift_right_w_4 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift4
.pend
shift_right_w_3 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift3
.pend

View File

@ -651,6 +651,18 @@ greatereq_w .proc
bmi equal_b._equal_b_false
.pend
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
func_exit .proc
; -- immediately exit the program with a return code in the A register
lda c64.ESTACK_LO+1,x
ldx orig_stackpointer
txs
rts ; return to original caller
.pend
func_read_flags .proc
; -- put the processor status register on the stack
php
@ -716,7 +728,7 @@ func_sin8 .proc
lda _sinecos8,y
sta c64.ESTACK_LO+1,x
rts
_sinecos8 .char 127 * sin(range(256+64) * rad(360.0/256.0))
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sin8u .proc
@ -724,7 +736,7 @@ func_sin8u .proc
lda _sinecos8u,y
sta c64.ESTACK_LO+1,x
rts
_sinecos8u .byte 128 + 127.5 * sin(range(256+64) * rad(360.0/256.0))
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sin16 .proc
@ -735,7 +747,7 @@ func_sin16 .proc
sta c64.ESTACK_HI+1,x
rts
_ := 32767 * sin(range(256+64) * rad(360.0/256.0))
_ := trunc(32767.0 * sin(range(256+64) * rad(360.0/256.0)))
_sinecos8lo .byte <_
_sinecos8hi .byte >_
.pend
@ -748,7 +760,7 @@ func_sin16u .proc
sta c64.ESTACK_HI+1,x
rts
_ := 32768 + 32767.5 * sin(range(256+64) * rad(360.0/256.0))
_ := trunc(32768.0 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)))
_sinecos8ulo .byte <_
_sinecos8uhi .byte >_
.pend
@ -1417,7 +1429,7 @@ _l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
rts
.pend
sort_b .proc
; 8bit signed sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
@ -1471,7 +1483,7 @@ _sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
sta _work3+1
dey
jmp _l2
_l1 dey
_l1 dey
dey
beq _l3
iny
@ -1506,12 +1518,12 @@ _l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
dec c64.SCRATCH_ZPB1
bne _sort_loop ;start working with the shorter sequence
rts
rts
_work1 .byte 0
_work3 .word 0
.pend
sort_w .proc
; 16bit signed sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
@ -1532,7 +1544,7 @@ _sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
sta _work3+1
dey
jmp _l2
_l1 dey
_l1 dey
dey
beq _l3
lda (c64.SCRATCH_ZPWORD1),y
@ -1543,7 +1555,7 @@ _l1 dey
sbc c64.SCRATCH_ZPWORD2+1
bvc +
eor #$80
+ bmi _l1
+ bmi _l1
_l2 sty _work1 ;index of potentially largest value
lda (c64.SCRATCH_ZPWORD1),y
sta c64.SCRATCH_ZPWORD2 ;potentially largest value
@ -1568,106 +1580,625 @@ _l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
dec c64.SCRATCH_ZPB1
bne _sort_loop ;start working with the shorter sequence
rts
rts
_work1 .byte 0
_work3 .word 0
.pend
.pend
reverse_b .proc
; --- reverse an array of bytes (in-place)
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
pha
_index_right = c64.SCRATCH_ZPWORD2
_index_left = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
sta _loop_count
lsr _loop_count
sec
sbc #1
sta _left_index
sta _index_right
lda #0
sta _right_index
pla
lsr a
tay
_loop sty c64.SCRATCH_ZPREG
ldy _left_index
sta _index_left
_loop ldy _index_right
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
ldy _index_left
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
ldy _index_right
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
ldy _index_left
sta (c64.SCRATCH_ZPWORD1),y
inc _right_index
dec _left_index
ldy c64.SCRATCH_ZPREG
dey
inc _index_left
dec _index_right
dec _loop_count
bne _loop
rts
.pend
reverse_f .proc
; --- reverse an array of floats
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
pha
sta c64.SCRATCH_ZPREG
asl a
asl a
clc
adc c64.SCRATCH_ZPREG ; *5 because float
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
; copy right index float to left index float
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
reverse_w .proc
; --- reverse an array of words (in-place)
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
_index_first = c64.SCRATCH_ZPWORD2
_index_second = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
pha
asl a ; *2 because words
sec
sbc #2
sta _left_index
sta _index_first
lda #0
sta _right_index
sta _index_second
pla
lsr a
pha
tay
sta _loop_count
; first reverse the lsbs
_loop_lo sty c64.SCRATCH_ZPREG
ldy _left_index
_loop_lo ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
inc _right_index
inc _right_index
dec _left_index
dec _left_index
ldy c64.SCRATCH_ZPREG
dey
inc _index_second
inc _index_second
dec _index_first
dec _index_first
dec _loop_count
bne _loop_lo
; now reverse the msbs
dec _right_index
inc _left_index
inc _left_index
inc _left_index
dec _index_second
inc _index_first
inc _index_first
inc _index_first
pla
tay
_loop_hi sty c64.SCRATCH_ZPREG
ldy _left_index
sta _loop_count
_loop_hi ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
dec _right_index
dec _right_index
inc _left_index
inc _left_index
ldy c64.SCRATCH_ZPREG
dey
dec _index_second
dec _index_second
inc _index_first
inc _index_first
dec _loop_count
bne _loop_hi
rts
rts
.pend
ror2_mem_ub .proc
; -- in-place 8-bit ror of byte at memory location on stack
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
lsr a
bcc +
ora #$80
+ sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_mem_ub .proc
; -- in-place 8-bit rol of byte at memory location on stack
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
cmp #$80
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsl_array_b .proc
; -- lsl a (u)byte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_ub .proc
; -- lsr a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_b .proc
; -- lsr a byte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsl_array_w .proc
; -- lsl a (u)word in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_uw .proc
; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_w .proc
; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol_array_ub .proc
; -- rol a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror_array_ub .proc
; -- ror a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror2_array_ub .proc
; -- ror2 (8-bit ror) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
bcc +
ora #$80
+ sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_ub .proc
; -- rol2 (8-bit rol) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
cmp #$80
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror_array_uw .proc
; -- ror a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
plp
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol_array_uw .proc
; -- rol a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
plp
rol a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_uw .proc
; -- rol2 (16-bit rol) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
dey
lda (c64.SCRATCH_ZPWORD1),y
adc #0
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend
ror2_array_uw .proc
; -- ror2 (16-bit ror) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
iny
lda (c64.SCRATCH_ZPWORD1),y
ora #$80
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend
strcpy .proc
; copy a string (0-terminated) from A/Y to (ZPWORD1)
; it is assumed the target string is large enough.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #$ff
- iny
lda (c64.SCRATCH_ZPWORD2),y
sta (c64.SCRATCH_ZPWORD1),y
bne -
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
lda c64.ESTACK_LO,x
tay ; length
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda #0
sta (c64.SCRATCH_ZPWORD2),y
- dey
cpy #$ff
bne +
rts
+ lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
jmp -
.pend
func_rightstr .proc
; rightstr(source, target, length) with params on stack
; make place for the 4 parameters for substr()
dex
dex
dex
dex
; X-> .
; x+1 -> length of segment
; x+2 -> start index
; X+3 -> target LO+HI
; X+4 -> source LO+HI
; original parameters:
; x+5 -> original length LO
; x+6 -> original targetLO + HI
; x+7 -> original sourceLO + HI
; replicate paramters:
lda c64.ESTACK_LO+5,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_LO+6,x
sta c64.ESTACK_LO+3,x
lda c64.ESTACK_HI+6,x
sta c64.ESTACK_HI+3,x
lda c64.ESTACK_LO+7,x
sta c64.ESTACK_LO+4,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+7,x
sta c64.ESTACK_HI+4,x
sta c64.SCRATCH_ZPWORD1+1
; determine string length
ldy #0
- lda (c64.SCRATCH_ZPWORD1),y
beq +
iny
bne -
+ tya
sec
sbc c64.ESTACK_LO+1,x ; start index = strlen - segment length
sta c64.ESTACK_LO+2,x
jsr func_substr
; unwind original params
inx
inx
inx
rts
.pend
func_substr .proc
; substr(source, target, start, length) with params on stack
inx
ldy c64.ESTACK_LO,x ; length
inx
lda c64.ESTACK_LO,x ; start
sta c64.SCRATCH_ZPB1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
; adjust src location
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ lda #0
sta (c64.SCRATCH_ZPWORD2),y
jmp _startloop
- lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
_startloop dey
cpy #$ff
bne -
rts
.pend

View File

@ -1,2 +1 @@
1.60
3.0

View File

@ -1,24 +1,25 @@
package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm
import java.io.IOException
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardWatchEventKinds
import java.util.*
import java.time.LocalDateTime
import kotlin.system.exitProcess
fun main(args: Array<String>) {
printSoftwareHeader("compiler")
if (args.isEmpty())
usage()
compileMain(args)
}
@ -29,51 +30,67 @@ internal fun printSoftwareHeader(what: String) {
}
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) {
var emulatorToStart = ""
var moduleFile = ""
var writeAssembly = true
var optimize = true
var launchAstVm = false
var watchMode = false
for (arg in args) {
if(arg=="-emu")
emulatorToStart = "x64"
else if(arg=="-emu2")
emulatorToStart = "x64sc"
else if(arg=="-noasm")
writeAssembly = false
else if(arg=="-noopt")
optimize = false
else if(arg=="-avm")
launchAstVm = true
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
val cli = CommandLineInterface("prog8compiler")
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try {
cli.parse(args)
} catch (e: Exception) {
exitProcess(1)
}
if(watchMode) {
if(moduleFile.isBlank())
usage()
when(compilationTarget) {
"c64" -> {
with(CompilationTarget) {
name = "c64"
machine = C64MachineDefinition
encodeString = { str, altEncoding ->
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
}
decodeString = { bytes, altEncoding ->
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
}
asmGenerator = ::AsmGen
}
}
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
exitProcess(1)
}
if(watchMode && moduleFiles.size<=1) {
val watchservice = FileSystems.getDefault().newWatchService()
while(true) {
val filepath = Paths.get(moduleFile).normalize()
val filepath = pathFrom(moduleFiles.single()).normalize()
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, optimize, writeAssembly)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
println(importedFile)
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("${Date()}: Waiting for file changes.")
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
val event = watchservice.take()
for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path
@ -87,51 +104,39 @@ private fun compileMain(args: Array<String>) {
}
} else {
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, optimize, writeAssembly)
if(!compilationResult.success)
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: AstException) {
exitProcess(1)
}
} catch (x: AstException) {
exitProcess(1)
}
if (launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(compilationResult.programAst)
vm.run()
}
if (emulatorToStart.isNotEmpty()) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
if (startEmulator) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
}
}
}
}
private fun usage() {
System.err.println("Missing argument(s):")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
}

View File

@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.StringDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
@ -79,7 +78,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun datatypeString(dt: DataType): String {
return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase()
DataType.STR -> dt.toString().toLowerCase()
DataType.ARRAY_UB -> "ubyte["
DataType.ARRAY_B -> "byte["
DataType.ARRAY_UW -> "uword["
@ -103,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return
when(decl.type) {
VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ")
@ -178,8 +183,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) {
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("")
stmt.accept(this)
output("\n")
@ -197,9 +200,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun printout(call: IFunctionCall) {
call.target.accept(this)
output("(")
for(arg in call.arglist) {
for(arg in call.args) {
arg.accept(this)
if(arg!==call.arglist.last())
if(arg!==call.args.last())
output(", ")
}
output(")")
@ -284,16 +287,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(assignment: Assignment) {
if(assignment is VariableInitializationAssignment) {
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
if(targetVar?.struct != null) {
// skip STRUCT init assignments
return
}
}
assignment.target.accept(this)
if (assignment.aug_op != null)
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
output(" ${assignment.aug_op} ")
else
output(" = ")
@ -315,10 +310,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(forLoop: ForLoop) {
output("for ")
if(forLoop.loopRegister!=null)
output(forLoop.loopRegister.toString())
else
forLoop.loopVar!!.accept(this)
forLoop.loopVar.accept(this)
output(" in ")
forLoop.iterable.accept(this)
output(" ")
@ -334,9 +326,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(repeatLoop: RepeatLoop) {
output("repeat ")
repeatLoop.iterations?.accept(this)
output(" ")
repeatLoop.body.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
output("do ")
untilLoop.body.accept(this)
output(" until ")
repeatLoop.untilCondition.accept(this)
untilLoop.untilCondition.accept(this)
}
override fun visit(returnStmt: Return) {
@ -352,12 +351,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(assignTarget: AssignTarget) {
if(assignTarget.register!=null)
output(assignTarget.register.toString())
else {
assignTarget.memoryAddress?.accept(this)
assignTarget.identifier?.accept(this)
}
assignTarget.memoryAddress?.accept(this)
assignTarget.identifier?.accept(this)
assignTarget.arrayindexed?.accept(this)
}
@ -398,10 +393,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}")
}
override fun visit(registerExpr: RegisterExpr) {
output(registerExpr.register.toString())
}
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name)
}
@ -436,10 +427,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputln("")
}
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) {
output("; NOP @ ${nopStatement.position} $nopStatement")
}

View File

@ -3,8 +3,9 @@ package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
@ -34,11 +35,13 @@ interface Node {
return this
throw FatalAstException("scope missing from $this")
}
fun replaceChildNode(node: Node, replacement: Node)
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<Expression>
var args: MutableList<Expression>
}
interface INameScope {
@ -49,32 +52,31 @@ interface INameScope {
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> subscopes[stmt.name] = stmt
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return subscopes
return null
}
fun getLabelOrVariable(name: String): Statement? {
@ -122,7 +124,7 @@ interface INameScope {
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
scope = scope?.subScope(name)
if(scope==null)
break
}
@ -130,7 +132,7 @@ interface INameScope {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as Statement?
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
@ -142,7 +144,7 @@ interface INameScope {
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
@ -153,20 +155,57 @@ interface INameScope {
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoVars() = statements.all { it !is VarDecl }
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
}
is UntilLoop -> find(it.body)
is RepeatLoop -> find(it.body)
is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
else -> { /* do nothing */ }
}
}
}
find(this)
return result
}
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
}
interface IAssignable {
// just a tag for now
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
class Program(val name: String, val modules: MutableList<Module>): Node {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val definedLoadAddress: Int
get() = modules.first().loadAddress
@ -174,17 +213,35 @@ class Program(val name: String, val modules: MutableList<Module>) {
var actualLoadAddress: Int = 0
fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
val mainBlocks = allBlocks().filter { it.name=="main" }
if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
mainBlocks[0].subScope("start") as Subroutine?
}
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(value) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(this)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = modules.indexOfFirst { it===node }
modules[idx] = replacement
replacement.parent = this
}
}
class Module(override val name: String,
@ -192,6 +249,7 @@ class Module(override val name: String,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
@ -205,10 +263,20 @@ class Module(override val name: String,
}
override fun definingScope(): INameScope = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Statement && replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
@ -219,6 +287,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
modules.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace")
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
@ -241,11 +313,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
}
// lookup something from the module.
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
}
}
}

View File

@ -7,7 +7,7 @@ import prog8.ast.Module
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
@ -19,13 +19,13 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : Statement {
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -142,6 +138,28 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
)
}
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun prog8Parser.StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
}
@ -175,8 +193,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val sub = subroutine()?.toAst()
if(sub!=null) return sub
val subroutine = subroutinedeclaration()?.toAst()
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
@ -187,46 +205,60 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val untilloop = untilloop()?.toAst()
if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturnvalues = returns.map { it.type }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(name, normalParameters, normalReturnvalues,
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
private class AsmSubroutineParameter(name: String,
@ -242,36 +274,52 @@ private class AsmSubroutineReturn(val type: DataType,
val stack: Boolean,
val position: Position)
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
= asmsub_return().map {
val register = it.identifier()?.toAst()
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag in $it")
}
}
AsmSubroutineReturn(
it.datatype().toAst(),
registerorpair,
statusregister,
!it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
AsmSubroutineParameter(vardecl.varname.text, datatype,
it.registerorpair()?.toAst(),
it.statusregister()?.toAst(),
val register = it.identifier()?.toAst()
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag in $it")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
!it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
FunctionCallStatement(location, mutableListOf(), void, toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
@ -280,11 +328,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
}
@ -295,11 +341,9 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
@ -318,44 +362,42 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
return when {
register!=null -> AssignTarget(register, null, null, null, toPosition())
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
}
}
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
val names = this.identifier().map { it.toAst().nameInSource.single() }
return names.map { CpuRegister.valueOf(it) }.toSet()
}
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str?.ALT_STRING_ENCODING() != null)
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
@ -408,7 +450,6 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
}
private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue()
@ -429,10 +470,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
@ -441,20 +485,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
StructLiteralValue(values, litval.toPosition())
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
}
if(register()!=null)
return RegisterExpr(register().toAst(), register().toPosition())
if(scoped_identifier()!=null)
return scoped_identifier().toAst()
@ -468,7 +505,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
@ -490,6 +528,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
throw FatalAstException(text)
}
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
@ -497,28 +537,22 @@ private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
@ -536,7 +570,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -549,25 +582,21 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val loopvar = identifier()?.toAst()
val loopvar = identifier().toAst()
val iterable = expression()!!.toAst()
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -576,13 +605,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(iterations, scope, toPosition())
}
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition())
return UntilLoop(scope, untilCondition, toPosition())
}
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
@ -641,4 +677,3 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}

View File

@ -1,7 +1,8 @@
package prog8.ast.base
import prog8.ast.Node
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/
@ -12,7 +13,6 @@ enum class DataType {
WORD, // pass by value
FLOAT, // pass by value
STR, // pass by reference
STR_S, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference
@ -24,15 +24,14 @@ enum class DataType {
* is the type assignable to the given other type?
*/
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
// what types are assignable to others, perhaps via a typecast, without loss of precision?
when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
STR -> targetType == STR
in ArrayDatatypes -> targetType == this
else -> false
}
@ -58,14 +57,14 @@ enum class DataType {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> MachineDefinition.Mflpt5.MemorySize
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> 2
else -> -9999999
}
}
}
enum class Register {
enum class CpuRegister {
A,
X,
Y
@ -77,14 +76,23 @@ enum class RegisterOrPair {
Y,
AX,
AY,
XY
XY;
companion object {
val names by lazy { values().map { it.toString()} }
}
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
Pc,
Pz,
Pv,
Pn
Pn;
companion object {
val names by lazy { values().map { it.toString()} }
}
}
enum class BranchCondition {
@ -112,10 +120,9 @@ val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
@ -123,12 +130,18 @@ val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
DataType.STR_S to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
DataType.WORD to DataType.ARRAY_W,
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)
@ -146,8 +159,15 @@ object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
companion object {
val DUMMY = Position("<dummy>", 0, 0, 0)
}
}

View File

@ -3,35 +3,42 @@ package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
class ErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
}
print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
fun handle() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
}
val msg = "${it.position} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
}
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
}

View File

@ -2,21 +2,17 @@ package prog8.ast.base
import prog8.ast.expressions.IdentifierReference
class FatalAstException (override var message: String) : Exception(message)
open class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
}
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -4,67 +4,79 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
// the name of the subroutine that should be called for every block to initialize its variables
internal const val initvarsSubName="prog8_init_vars"
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.visit(this)
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.anonscopeVarsCleanup() {
val mover = AnonymousScopeVarsCleanup(this)
mover.visit(this)
printErrors(mover.result(), name)
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
initvalueCreator.visit(this)
val checker = StatementReorderer(this)
checker.visit(this)
val reorder = StatementReorderer(this)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Program.transformAssignments(errors: ErrorReporter) {
val transform = AssignmentTransformer(this, errors)
transform.visit(this)
while(transform.optimizationsDone>0 && errors.isEmpty()) {
transform.applyModifications()
transform.optimizationsDone = 0
transform.visit(this)
}
transform.applyModifications()
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
checker.visit(this)
printErrors(checker.result(), name)
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
printErrors(checker.result(), name)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(this)
checker.visit(this)
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
if(errors.isEmpty()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
}
printErrors(checker.result(), name)
if (modules.map { it.name }.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
}
internal fun Program.variousCleanups() {
val process = VariousCleanups()
process.visit(this)
process.applyModifications()
}

View File

@ -1,21 +1,14 @@
package prog8.ast.expressions
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.CannotEvaluateException
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.*
@ -27,30 +20,28 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifiers(vararg name: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(other: Expression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
return when(this) {
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
(other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
(other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
else -> return other==this
else -> other==this
}
}
}
@ -64,11 +55,38 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
expression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression)
expression = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType = expression.inferType(program)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
"+" -> inferred
"~", "not" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
else -> inferred
}
}
"-" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.BYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.WORD)
else -> inferred
}
}
else -> throw FatalAstException("weird prefix expression operator")
}
}
override fun toString(): String {
return "Prefix($operator $expression)"
@ -84,6 +102,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
right.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
node===left -> left = replacement
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
}
replacement.parent = this
}
override fun toString(): String {
return "[$left $operator $right]"
}
@ -91,8 +119,9 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
@ -185,7 +214,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
override val position: Position) : Expression() {
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
@ -193,16 +222,26 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
arrayspec.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayspec.index -> arrayspec.index = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
@ -223,8 +262,15 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
expression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
@ -247,14 +293,20 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -262,8 +314,15 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
this.addressExpression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@ -307,17 +366,21 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
}
val asBooleanValue: Boolean = number!=0
val asBooleanValue: Boolean = number.toDouble() != 0.0
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
@ -392,79 +455,66 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
}
class StructLiteralValue(var values: List<Expression>,
override val position: Position): Expression() {
override lateinit var parent: Node
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
override fun linkParents(parent: Node) {
this.parent=parent
values.forEach { it.linkParents(this) }
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }"
}
}
class StringLiteralValue(val type: DataType, // only string types
val value: String,
initHeapId: Int? =null,
class StringLiteralValue(val value: String,
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
override val position: Position) : Expression() {
override lateinit var parent: Node
var heapId = initHeapId
private set
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, type)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is StringLiteralValue)
return false
return value==other.value && type==other.type
}
fun addToHeap(heap: HeapValues) {
if (heapId != null)
return
else
heapId = heap.addString(type, value)
return value==other.value && altEncoding == other.altEncoding
}
}
class ArrayLiteralValue(val type: DataType, // only array types
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
val value: Array<Expression>,
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node
var heapId = initHeapId
private set
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOfFirst { it===node }
value[idx] = replacement
replacement.parent = this
}
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
@ -473,17 +523,45 @@ class ArrayLiteralValue(val type: DataType, // only array types
return type==other.type && value.contentEquals(other.value)
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
val forloop = parent as? ForLoop
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
InferredTypes.InferredType.unknown()
else
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
}
}
// otherwise, select the "biggegst" datatype based on the elements in the array.
val datatypesInArray = value.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
else -> InferredTypes.InferredType.unknown()
}
}
fun cast(targettype: DataType): ArrayLiteralValue? {
if(type==targettype)
if(type.istype(targettype))
return this
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val castArray = value.map{
val num = it as? NumericLiteralValue
if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if (elementType != DataType.UWORD || it !is AddressOf)
throw FatalAstException("weird array element $it")
return null
it
} else {
try {
@ -493,38 +571,10 @@ class ArrayLiteralValue(val type: DataType, // only array types
}
}
}.toTypedArray()
return ArrayLiteralValue(targettype, castArray, position = position)
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
return null // invalid type conversion from $this to $targettype
}
fun addToHeap(heap: HeapValues) {
if (heapId != null)
return
else {
if(value.any {it is AddressOf }) {
val intArrayWithAddressOfs = value.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
else -> throw FatalAstException("invalid datatype in array")
}
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = value.map { (it as? NumericLiteralValue)?.number }
if(null !in valuesInArray) {
heapId = if (type == DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it!!.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
}
}
class RangeExpr(var from: Expression,
@ -540,9 +590,21 @@ class RangeExpr(var from: Expression,
step.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
from===node -> from=replacement
to===node -> to=replacement
step===node -> step=replacement
else -> throw FatalAstException("invalid replacement")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
@ -552,7 +614,6 @@ class RangeExpr(var from: Expression,
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt istype DataType.STR_S && toDt istype DataType.STR_S -> InferredTypes.knownFor(DataType.STR_S)
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
@ -576,9 +637,9 @@ class RangeExpr(var from: Expression,
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromString.value, true)[0].toInt()
toVal = Petscii.encodePetscii(toString.value, true)[0].toInt()
// string range -> int range over character values
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
@ -608,25 +669,7 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
}
}
class RegisterExpr(val register: Register, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
fun targetStatement(namespace: INameScope) =
@ -638,10 +681,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun hashCode() = nameInSource.hashCode()
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
@ -658,16 +708,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
return "IdentifierRef($nameInSource)"
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) {
return InferredTypes.knownFor(targetStmt.datatype)
return if(targetStmt is VarDecl) {
InferredTypes.knownFor(targetStmt.datatype)
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
InferredTypes.InferredType.unknown()
}
}
@ -678,22 +729,32 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
is IdentifierReference -> value.heapId(namespace)
is StringLiteralValue -> value.heapId ?: throw FatalAstException("string is not on the heap: $value")
is ArrayLiteralValue -> value.heapId ?: throw FatalAstException("array is not on the heap: $value")
is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
else -> throw FatalAstException("requires a reference value")
}
}
}
class FunctionCall(override var target: IdentifierReference,
override var arglist: MutableList<Expression>,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
args.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program) = constValue(program, true)
@ -708,7 +769,7 @@ class FunctionCall(override var target: IdentifierReference,
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program)
resultValue = exprfunc(args, position, program)
else if(func.returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
@ -726,15 +787,20 @@ class FunctionCall(override var target: IdentifierReference,
// const-evaluating the builtin function call failed.
return null
}
catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
return null
}
}
override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)"
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)
@ -747,7 +813,7 @@ class FunctionCall(override var target: IdentifierReference,
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return InferredTypes.void() // these have no return value
}
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
return builtinFunctionReturnType(target.nameInSource[0], this.args, program)
}
is Subroutine -> {
if(stmt.returntypes.isEmpty())

View File

@ -1,12 +1,13 @@
package prog8.ast.expressions
import prog8.ast.base.DataType
import java.util.*
object InferredTypes {
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
init {
if(datatype!=null && (isUnknown || isVoid))
throw IllegalArgumentException("invalid combination of args")
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
}
val isKnown = datatype!=null
@ -29,9 +30,11 @@ object InferredTypes {
return when {
datatype!=null -> datatype.toString()
isVoid -> "<void>"
else -> "<unkonwn>"
else -> "<unknown>"
}
}
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
}
private val unknownInstance = InferredType.unknown()
@ -43,7 +46,6 @@ object InferredTypes {
DataType.WORD to InferredType.known(DataType.WORD),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.STR to InferredType.known(DataType.STR),
DataType.STR_S to InferredType.known(DataType.STR_S),
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),

File diff suppressed because it is too large Load Diff

View File

@ -1,113 +1,81 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
return checkResult
}
private fun nameError(name: String, position: Position, existing: Statement) {
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
}
override fun visit(module: Module) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different module
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(block: Block): Statement {
override fun visit(block: Block) {
val existing = blocks[block.name]
if(existing!=null)
nameError(block.name, block.position, existing)
else
blocks[block.name] = block
return super.visit(block)
super.visit(block)
}
override fun visit(functionCall: FunctionCall): Expression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent)
return super.visit(typecast)
}
return super.visit(functionCall)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) }
// now check the identifier
if(decl.name in BuiltinFunctions)
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
if(decl.name in CompilationTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
if (decl.struct == null) {
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) {
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
if (decl.value != null && decl.value !is ArrayLiteralValue) {
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
return super.visit(decl)
}
}
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
return super.visit(decl)
super.visit(decl)
}
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in AssemblyProgram.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
@ -139,296 +107,50 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
nameError(name, sub.position, subroutine)
}
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
// NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
}
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
}
return super.visit(subroutine)
super.visit(subroutine)
}
override fun visit(label: Label): Statement {
if(label.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
override fun visit(label: Label) {
if(label.name in CompilationTarget.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
checkResult.add(NameError("builtin function cannot be redefined", label.position))
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
}
return super.visit(label)
}
override fun visit(forLoop: ForLoop): Statement {
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
// rather than reusing an already declared loopvar from an outer scope.
// For loops that loop over an interable variable (instead of a range of numbers) get an
// additional interation count variable in their scope.
if(forLoop.loopRegister!=null) {
if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else {
val loopVar = forLoop.loopVar
if (loopVar != null) {
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
if (existing == null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
}
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
for(el in existing) {
if(el === label || el.name != label.name)
continue
else {
nameError(label.name, label.position, el)
break
}
}
}
return super.visit(forLoop)
super.visit(label)
}
override fun visit(assignTarget: AssignTarget): AssignTarget {
if(assignTarget.register== Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.visit(assignTarget)
override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
super.visit(string)
}
override fun visit(returnStmt: Return): Statement {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) {
newValue = lval.cast(subroutine.returntypes.single())
} else {
newValue = returnStmt.value!!
}
returnStmt.value = newValue
}
return super.visit(returnStmt)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
return if (vardecl!=null) {
fixupArrayDatatype(array, vardecl, program)
} else if(array.heapId!=null) {
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
// (we don't know the desired datatype here exactly so we guess)
val datatype = determineArrayDt(array.value)
val litval2 = array.cast(datatype)!!
litval2.parent = array.parent
// finally, replace the literal array by a identifier reference.
makeIdentifierFromRefLv(litval2)
} else
array
}
return array
}
override fun visit(stringLiteral: StringLiteralValue): Expression {
val string = super.visit(stringLiteral)
if(string is StringLiteralValue) {
val vardecl = string.parent as? VarDecl
// intern the string; move it into the heap
if (string.value.length !in 1..255)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
else {
string.addToHeap(program.heap)
}
return if (vardecl != null)
string
else
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
}
return string
}
private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
if(datatypesInArray.isEmpty() || datatypesInArray.any { !it.isKnown })
throw IllegalArgumentException("can't determine type of empty array")
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
array.addToHeap(program.heap)
val scope = array.definingScope()
val variable = VarDecl.createAuto(array)
return replaceWithIdentifier(variable, scope, array.parent)
}
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
string.addToHeap(program.heap)
val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
}
override fun visit(structDecl: StructDecl): Statement {
override fun visit(structDecl: StructDecl) {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
errors.err("structs can only contain numerical types", decl.position)
}
return super.visit(structDecl)
super.visit(structDecl)
}
override fun visit(expr: BinaryExpression): Expression {
return when {
expr.left is StringLiteralValue ->
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is StringLiteralValue ->
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr)
}
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
string.value.repeat(constvalue.number.toInt()), null, expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
"${string.value}${operand.value}", null, expr.position)
}
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
if(dts.any { it !in NumericDatatypes }) {
return array
}
val dt = when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
if(dt==array.type)
return array
// convert values and array type
val elementType = ArrayElementTypes.getValue(dt)
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression}.toTypedArray()
val array2 = ArrayLiteralValue(dt, values, array.heapId, array.position)
array2.linkParents(array.parent)
return array2
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, program: Program): ArrayLiteralValue {
if(array.heapId!=null) {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val litval2 =
try {
val result = array.cast(vardecl.datatype)
if(result==null) {
val constElements = array.value.mapNotNull { it.constValue(program) }
val elementDts = constElements.map { it.type }
if(DataType.FLOAT in elementDts) {
array.cast(DataType.ARRAY_F) ?: ArrayLiteralValue(DataType.ARRAY_F, array.value, array.heapId, array.position)
} else {
val numbers = constElements.map { it.number.toInt() }
val minValue = numbers.min()!!
val maxValue = numbers.max()!!
if (minValue >= 0) {
// only positive values, so uword or ubyte
val dt = if(maxValue<256) DataType.ARRAY_UB else DataType.ARRAY_UW
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
} else {
// negative value present, so word or byte
val dt = if(minValue >= -128 && maxValue<=127) DataType.ARRAY_B else DataType.ARRAY_W
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
}
}
}
else result
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap(program.heap)
return litval2
}
}
return array
}

View File

@ -1,21 +1,23 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.AstException
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
internal fun result(): List<AstException> {
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return emptyList()
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@ -44,7 +46,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()

View File

@ -0,0 +1,176 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource == listOf("swap")) {
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
// otherwise:
// rewrite swap(x,y) as follows:
// - declare a temp variable of the same datatype
// - temp = x, x = y, y= temp
val first = functionCallStatement.args[0]
val second = functionCallStatement.args[1]
if(first !is IdentifierReference && second !is IdentifierReference) {
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
val tempvar = IdentifierReference(listOf(tempname), first.position)
val assignTemp = Assignment(
AssignTarget(tempvar, null, null, first.position),
null,
first,
first.position
)
val assignFirst = Assignment(
AssignTarget.fromExpr(first),
null,
second,
first.position
)
val assignSecond = Assignment(
AssignTarget.fromExpr(second),
null,
tempvar,
first.position
)
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
}
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
return listOf(IAstModification.ReplaceNode(
functionCall, typecast, parent
))
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
return listOf(IAstModification.ReplaceNode(
decl, result, parent
))
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
}
}
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when {
expr.left is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
parent
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
}
return noModifications
}
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast!=array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null && litval2!=array) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
)
}
}
}
return noModifications
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
}

View File

@ -0,0 +1,437 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
} else {
throw FatalAstException("parent of a remove modification is not an INameScope")
}
}
}
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(0, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(val expr: BinaryExpression): IAstModification {
override fun perform() {
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
}
}
}
abstract class AstWalker {
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
for (it in mods) modifications += Triple(it, node, parent)
}
fun applyModifications(): Int {
modifications.forEach {
it.first.perform()
}
val amount = modifications.size
modifications.clear()
return amount
}
fun visit(program: Program) {
track(before(program, program), program, program)
program.modules.forEach { it.accept(this, program) }
track(after(program, program), program, program)
}
fun visit(module: Module, parent: Node) {
track(before(module, parent), module, parent)
module.statements.forEach{ it.accept(this, module) }
track(after(module, parent), module, parent)
}
fun visit(expr: PrefixExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.expression.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(expr: BinaryExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.left.accept(this, expr)
expr.right.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(directive: Directive, parent: Node) {
track(before(directive, parent), directive, parent)
track(after(directive, parent), directive, parent)
}
fun visit(block: Block, parent: Node) {
track(before(block, parent), block, parent)
block.statements.forEach { it.accept(this, block) }
track(after(block, parent), block, parent)
}
fun visit(decl: VarDecl, parent: Node) {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
track(after(decl, parent), decl, parent)
}
fun visit(subroutine: Subroutine, parent: Node) {
track(before(subroutine, parent), subroutine, parent)
subroutine.statements.forEach { it.accept(this, subroutine) }
track(after(subroutine, parent), subroutine, parent)
}
fun visit(functionCall: FunctionCall, parent: Node) {
track(before(functionCall, parent), functionCall, parent)
functionCall.target.accept(this, functionCall)
functionCall.args.forEach { it.accept(this, functionCall) }
track(after(functionCall, parent), functionCall, parent)
}
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
track(before(functionCallStatement, parent), functionCallStatement, parent)
functionCallStatement.target.accept(this, functionCallStatement)
functionCallStatement.args.forEach { it.accept(this, functionCallStatement) }
track(after(functionCallStatement, parent), functionCallStatement, parent)
}
fun visit(identifier: IdentifierReference, parent: Node) {
track(before(identifier, parent), identifier, parent)
track(after(identifier, parent), identifier, parent)
}
fun visit(jump: Jump, parent: Node) {
track(before(jump, parent), jump, parent)
jump.identifier?.accept(this, jump)
track(after(jump, parent), jump, parent)
}
fun visit(ifStatement: IfStatement, parent: Node) {
track(before(ifStatement, parent), ifStatement, parent)
ifStatement.condition.accept(this, ifStatement)
ifStatement.truepart.accept(this, ifStatement)
ifStatement.elsepart.accept(this, ifStatement)
track(after(ifStatement, parent), ifStatement, parent)
}
fun visit(branchStatement: BranchStatement, parent: Node) {
track(before(branchStatement, parent), branchStatement, parent)
branchStatement.truepart.accept(this, branchStatement)
branchStatement.elsepart.accept(this, branchStatement)
track(after(branchStatement, parent), branchStatement, parent)
}
fun visit(range: RangeExpr, parent: Node) {
track(before(range, parent), range, parent)
range.from.accept(this, range)
range.to.accept(this, range)
range.step.accept(this, range)
track(after(range, parent), range, parent)
}
fun visit(label: Label, parent: Node) {
track(before(label, parent), label, parent)
track(after(label, parent), label, parent)
}
fun visit(numLiteral: NumericLiteralValue, parent: Node) {
track(before(numLiteral, parent), numLiteral, parent)
track(after(numLiteral, parent), numLiteral, parent)
}
fun visit(string: StringLiteralValue, parent: Node) {
track(before(string, parent), string, parent)
track(after(string, parent), string, parent)
}
fun visit(array: ArrayLiteralValue, parent: Node) {
track(before(array, parent), array, parent)
array.value.forEach { v->v.accept(this, array) }
track(after(array, parent), array, parent)
}
fun visit(assignment: Assignment, parent: Node) {
track(before(assignment, parent), assignment, parent)
assignment.target.accept(this, assignment)
assignment.value.accept(this, assignment)
track(after(assignment, parent), assignment, parent)
}
fun visit(postIncrDecr: PostIncrDecr, parent: Node) {
track(before(postIncrDecr, parent), postIncrDecr, parent)
postIncrDecr.target.accept(this, postIncrDecr)
track(after(postIncrDecr, parent), postIncrDecr, parent)
}
fun visit(contStmt: Continue, parent: Node) {
track(before(contStmt, parent), contStmt, parent)
track(after(contStmt, parent), contStmt, parent)
}
fun visit(breakStmt: Break, parent: Node) {
track(before(breakStmt, parent), breakStmt, parent)
track(after(breakStmt, parent), breakStmt, parent)
}
fun visit(forLoop: ForLoop, parent: Node) {
track(before(forLoop, parent), forLoop, parent)
forLoop.loopVar.accept(this, forLoop)
forLoop.iterable.accept(this, forLoop)
forLoop.body.accept(this, forLoop)
track(after(forLoop, parent), forLoop, parent)
}
fun visit(whileLoop: WhileLoop, parent: Node) {
track(before(whileLoop, parent), whileLoop, parent)
whileLoop.condition.accept(this, whileLoop)
whileLoop.body.accept(this, whileLoop)
track(after(whileLoop, parent), whileLoop, parent)
}
fun visit(repeatLoop: RepeatLoop, parent: Node) {
track(before(repeatLoop, parent), repeatLoop, parent)
repeatLoop.iterations?.accept(this, repeatLoop)
repeatLoop.body.accept(this, repeatLoop)
track(after(repeatLoop, parent), repeatLoop, parent)
}
fun visit(untilLoop: UntilLoop, parent: Node) {
track(before(untilLoop, parent), untilLoop, parent)
untilLoop.untilCondition.accept(this, untilLoop)
untilLoop.body.accept(this, untilLoop)
track(after(untilLoop, parent), untilLoop, parent)
}
fun visit(returnStmt: Return, parent: Node) {
track(before(returnStmt, parent), returnStmt, parent)
returnStmt.value?.accept(this, returnStmt)
track(after(returnStmt, parent), returnStmt, parent)
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}
fun visit(assignTarget: AssignTarget, parent: Node) {
track(before(assignTarget, parent), assignTarget, parent)
assignTarget.arrayindexed?.accept(this, assignTarget)
assignTarget.identifier?.accept(this, assignTarget)
assignTarget.memoryAddress?.accept(this, assignTarget)
track(after(assignTarget, parent), assignTarget, parent)
}
fun visit(scope: AnonymousScope, parent: Node) {
track(before(scope, parent), scope, parent)
scope.statements.forEach { it.accept(this, scope) }
track(after(scope, parent), scope, parent)
}
fun visit(typecast: TypecastExpression, parent: Node) {
track(before(typecast, parent), typecast, parent)
typecast.expression.accept(this, typecast)
track(after(typecast, parent), typecast, parent)
}
fun visit(memread: DirectMemoryRead, parent: Node) {
track(before(memread, parent), memread, parent)
memread.addressExpression.accept(this, memread)
track(after(memread, parent), memread, parent)
}
fun visit(memwrite: DirectMemoryWrite, parent: Node) {
track(before(memwrite, parent), memwrite, parent)
memwrite.addressExpression.accept(this, memwrite)
track(after(memwrite, parent), memwrite, parent)
}
fun visit(addressOf: AddressOf, parent: Node) {
track(before(addressOf, parent), addressOf, parent)
addressOf.identifier.accept(this, addressOf)
track(after(addressOf, parent), addressOf, parent)
}
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
track(before(inlineAssembly, parent), inlineAssembly, parent)
track(after(inlineAssembly, parent), inlineAssembly, parent)
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
}
fun visit(nopStatement: NopStatement, parent: Node) {
track(before(nopStatement, parent), nopStatement, parent)
track(after(nopStatement, parent), nopStatement, parent)
}
fun visit(whenStatement: WhenStatement, parent: Node) {
track(before(whenStatement, parent), whenStatement, parent)
whenStatement.condition.accept(this, whenStatement)
whenStatement.choices.forEach { it.accept(this, whenStatement) }
track(after(whenStatement, parent), whenStatement, parent)
}
fun visit(whenChoice: WhenChoice, parent: Node) {
track(before(whenChoice, parent), whenChoice, parent)
whenChoice.values?.forEach { it.accept(this, whenChoice) }
whenChoice.statements.accept(this, whenChoice)
track(after(whenChoice, parent), whenChoice, parent)
}
fun visit(structDecl: StructDecl, parent: Node) {
track(before(structDecl, parent), structDecl, parent)
structDecl.statements.forEach { it.accept(this, structDecl) }
track(after(structDecl, parent), structDecl, parent)
}
}

View File

@ -1,263 +0,0 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
}
fun visit(module: Module) {
module.statements = module.statements.map { it.accept(this) }.toMutableList()
}
fun visit(expr: PrefixExpression): Expression {
expr.expression = expr.expression.accept(this)
return expr
}
fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this)
return expr
}
fun visit(directive: Directive): Statement {
return directive
}
fun visit(block: Block): Statement {
block.statements = block.statements.map { it.accept(this) }.toMutableList()
return block
}
fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this)
return decl
}
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
return subroutine
}
fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
return functionCall
}
fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
return functionCallStatement
}
fun visit(identifier: IdentifierReference): Expression {
// note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier
}
fun visit(jump: Jump): Statement {
if(jump.identifier!=null) {
val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement
}
fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement
}
fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this)
range.to = range.to.accept(this)
range.step = range.step.accept(this)
return range
}
fun visit(label: Label): Statement {
return label
}
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
return literalValue
}
fun visit(stringLiteral: StringLiteralValue): Expression {
return stringLiteral
}
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
for(av in arrayLiteral.value.withIndex()) {
val newvalue = av.value.accept(this)
arrayLiteral.value[av.index] = newvalue
}
return arrayLiteral
}
fun visit(assignment: Assignment): Statement {
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this)
return assignment
}
fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr
}
fun visit(contStmt: Continue): Statement {
return contStmt
}
fun visit(breakStmt: Break): Statement {
return breakStmt
}
fun visit(forLoop: ForLoop): Statement {
val newloopvar = forLoop.loopVar?.accept(this)
when(newloopvar) {
is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar")
}
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
}
fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop
}
fun visit(returnStmt: Return): Statement {
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
val ident = arrayIndexedExpression.identifier.accept(this)
if(ident is IdentifierReference)
arrayIndexedExpression.identifier = ident
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
}
fun visit(assignTarget: AssignTarget): AssignTarget {
val ident = assignTarget.identifier?.accept(this)
when (ident) {
is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier")
}
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
assignTarget.memoryAddress?.let { visit(it) }
return assignTarget
}
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
return scope
}
fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this)
return typecast
}
fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this)
return memread
}
fun visit(memwrite: DirectMemoryWrite) {
memwrite.addressExpression = memwrite.addressExpression.accept(this)
}
fun visit(addressOf: AddressOf): Expression {
val ident = addressOf.identifier.accept(this)
if(ident is IdentifierReference)
addressOf.identifier = ident
else
throw FatalAstException("can't change class of addressof identifier")
return addressOf
}
fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly
}
fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder
}
fun visit(nopStatement: NopStatement): Statement {
return nopStatement
}
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition = whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
}
fun visit(whenChoice: WhenChoice) {
whenChoice.values = whenChoice.values?.map { it.accept(this) }
val stmt = whenChoice.statements.accept(this)
if(stmt is AnonymousScope)
whenChoice.statements = stmt
else {
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
whenChoice.statements.linkParents(whenChoice)
}
}
fun visit(structDecl: StructDecl): Statement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): Expression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
}

View File

@ -7,7 +7,7 @@ import prog8.ast.statements.*
interface IAstVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
program.modules.forEach { it.accept(this) }
}
fun visit(module: Module) {
@ -41,12 +41,12 @@ interface IAstVisitor {
fun visit(functionCall: FunctionCall) {
functionCall.target.accept(this)
functionCall.arglist.forEach { it.accept(this) }
functionCall.args.forEach { it.accept(this) }
}
fun visit(functionCallStatement: FunctionCallStatement) {
functionCallStatement.target.accept(this)
functionCallStatement.arglist.forEach { it.accept(this) }
functionCallStatement.args.forEach { it.accept(this) }
}
fun visit(identifier: IdentifierReference) {
@ -102,7 +102,7 @@ interface IAstVisitor {
}
fun visit(forLoop: ForLoop) {
forLoop.loopVar?.accept(this)
forLoop.loopVar.accept(this)
forLoop.iterable.accept(this)
forLoop.body.accept(this)
}
@ -113,10 +113,15 @@ interface IAstVisitor {
}
fun visit(repeatLoop: RepeatLoop) {
repeatLoop.untilCondition.accept(this)
repeatLoop.iterations?.accept(this)
repeatLoop.body.accept(this)
}
fun visit(untilLoop: UntilLoop) {
untilLoop.untilCondition.accept(this)
untilLoop.body.accept(this)
}
fun visit(returnStmt: Return) {
returnStmt.value?.accept(this)
}
@ -155,9 +160,6 @@ interface IAstVisitor {
fun visit(inlineAssembly: InlineAssembly) {
}
fun visit(registerExpr: RegisterExpr) {
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
}
@ -177,8 +179,4 @@ interface IAstVisitor {
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
}

View File

@ -1,36 +1,21 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.Node
import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
internal fun result(): List<SyntaxError> {
return checkResult
}
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
override fun visit(module: Module) {
super.visit(module)
val newStatements : MutableList<Statement> = mutableListOf()
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
for (sourceStmt in module.statements) {
val stmt = sourceStmt.accept(this)
if(stmt is Directive && stmt.parent is Module) {
if(stmt.directive in moduleLevelDirectives) {
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
continue
}
}
newStatements.add(stmt)
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent))
}
module.statements = newStatements
return noModifications
}
}

View File

@ -0,0 +1,71 @@
package prog8.ast.processing
/*
This is here for reference only, reflection based ast walking is very slow
when compared to the more verbose visitor pattern interfaces.
Too bad, because the code is very small
*/
//import prog8.ast.NoAstWalk
//import prog8.ast.Node
//import prog8.ast.Program
//import prog8.ast.base.Position
//import prog8.ast.expressions.BinaryExpression
//import prog8.ast.expressions.NumericLiteralValue
//import kotlin.reflect.KClass
//import kotlin.reflect.KVisibility
//import kotlin.reflect.full.declaredMemberProperties
//import kotlin.reflect.full.isSubtypeOf
//import kotlin.reflect.full.starProjectedType
//
//
//class ReflectionAstWalker {
// private val nodeType = Node::class.starProjectedType
// private val collectionType = Collection::class.starProjectedType
//
//
// fun walk(node: Node, nesting: Int) {
// val nodetype: KClass<out Node> = node::class
// val indent = " ".repeat(nesting)
// //println("$indent VISITING ${nodetype.simpleName}")
// val visibleAstMembers = nodetype.declaredMemberProperties.filter {
// it.visibility!=KVisibility.PRIVATE && !it.isLateinit &&
// !(it.annotations.any{a->a is NoAstWalk})
// }
// for(prop in visibleAstMembers) {
// if(prop.returnType.isSubtypeOf(nodeType)) {
// // println("$indent +PROP: ${prop.name}")
// walk(prop.call(node) as Node, nesting + 1)
// }
// else if(prop.returnType.isSubtypeOf(collectionType)) {
// val elementType = prop.returnType.arguments.single().type
// if(elementType!=null && elementType.isSubtypeOf(nodeType)) {
// val nodes = prop.call(node) as Collection<Node>
// nodes.forEach { walk(it, nesting+1) }
// }
// }
// }
// }
// fun walk(program: Program) {
// for(module in program.modules) {
// println("---MODULE $module---")
// walk(module, 0)
// }
// }
//}
//
//
//fun main() {
// val ast = BinaryExpression(
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
// "+",
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
// Position.DUMMY
// )
//
// val walker = ReflectionAstWalker()
// walker.walk(ast,0)
//
//}

View File

@ -1,259 +1,184 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.mangledStructMemberName
import prog8.ast.statements.*
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when {
structAssignment.value is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
structAssignment.value is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
internal class StatementReorderer(val program: Program) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end.
// - in every scope:
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
// -- all vardecls then follow.
// -- the remaining statements then follow in their original order.
//
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) augmented assignment is turned into regular assignment.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val noModifications = emptyList<IAstModification>()
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
override fun visit(module: Module) {
addReturns.clear()
super.visit(module)
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
val nonLibraryBlocks = module.statements.withIndex()
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.remove(mainBlock)
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
val varDecls = module.statements.filterIsInstance<VarDecl>()
module.statements.removeAll(varDecls)
module.statements.addAll(0, varDecls)
reorderVardeclsAndDirectives(module.statements)
return noModifications
}
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
for(pos in addReturns) {
println(pos)
val returnStmt = Return(null, pos.first.position)
returnStmt.linkParents(pos.first as Node)
pos.first.statements.add(pos.second, returnStmt)
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
reorderVardeclsAndDirectives(block.statements)
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
)
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null) {
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
val assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
} else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
}
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements)
throw FatalAstException("element count mismatch")
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
null, sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
override fun visit(block: Block): Statement {
val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0
// move all subroutines to the end of the block
for (subroutine in subroutines) {
if(subroutine.name!="start" || block.name!="main") {
block.remove(subroutine)
block.statements.add(subroutine)
}
numSubroutinesAtEnd++
}
// move the "start" subroutine to the top
if(block.name=="main") {
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
block.remove(it)
block.statements.add(0, it)
numSubroutinesAtEnd--
}
}
// make sure there is a 'return' in front of the first subroutine
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
if(firstSub.name != "start" && block.name != "main") {
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
if (stmtBeforeFirstSub !is Return
&& stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
// create subroutine that initializes the block's variables (if any)
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
if(varInits.isNotEmpty()) {
val statements = varInits.map{it.value}.toMutableList()
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
emptySet(), null, false, statements, block.position)
varInitSub.keepAlways = true
varInitSub.linkParents(block)
block.statements.add(varInitSub)
// remove the varinits from the block's statements
for(index in varInits.map{it.index}.reversed())
block.statements.removeAt(index)
}
return super.visit(block)
}
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
val scope = subroutine.definingScope()
if(scope is Subroutine) {
for(stmt in scope.statements.withIndex()) {
if(stmt.index>0 && stmt.value===subroutine) {
val precedingStmt = scope.statements[stmt.index-1]
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
addReturns.add(Pair(scope, stmt.index))
}
}
}
}
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls)
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
subroutine.statements.removeAll(directives)
subroutine.statements.addAll(0, directives)
if(subroutine.returntypes.isEmpty()) {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(null, subroutine.position)
returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt)
}
}
}
return subroutine
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
// struct assignments will be flattened (if it's not a struct literal)
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
if (assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if (assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
}
}
if(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return assg
}
}

View File

@ -2,197 +2,202 @@ package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.printWarning
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
*/
override fun visit(expr: BinaryExpression): Expression {
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.right.inferType(program)
private val noModifications = emptyList<IAstModification>()
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
return noModifications
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program, assignment)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number.toDouble()
// more complex comparisons if the type is different, but the constant value is compatible
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
}
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
return assg
return noModifications
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
for(arg in sub.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val requiredType = arg.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.arglist.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
break
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
} else if(arg.second.value is NumericLiteralValue) {
try {
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
castedValue,
call as Node)
} catch (x: ExpressionError) {
// no cast possible
}
}
}
}
}
}
null -> {}
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return super.visit(typecast)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
for (arg in func.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node)
}
}
}
}
}
null -> { }
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
return litval
return modifications
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return noModifications
}
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
}
return noModifications
}
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
}
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
// add a typecast to the return type if it doesn't match the subroutine's signature
val returnValue = returnStmt.value
if(returnValue!=null) {
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType))
return noModifications
if (returnValue is NumericLiteralValue) {
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
} else {
return listOf(IAstModification.ReplaceNode(
returnValue,
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
returnStmt))
}
}
}
return noModifications
}
}

View File

@ -1,157 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(decl: VarDecl): Statement {
super.visit(decl)
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ArrayLiteralValue(decl.datatype,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap(program.heap)
decl.value = array
}
if(decl.type!= VarDeclType.VAR || decl.value==null)
return decl
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is NumericLiteralValue)
declvalue.cast(decl.datatype)
else
declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
value,
decl.position
)
}
return decl
}
override fun visit(functionCall: FunctionCall): Expression {
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
}
return functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
}
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? StringLiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
}
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf
addrOf.linkParents(parent)
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
}

View File

@ -0,0 +1,43 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
}
return noModifications
}
}

View File

@ -0,0 +1,42 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall)
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
override fun visit(functionCallStatement: FunctionCallStatement)
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
private fun checkTypes(call: IFunctionCall, scope: INameScope) {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
when(target) {
is Subroutine -> {
val paramtypes = target.parameters.map { it.type }
if(argtypes!=paramtypes)
throw CompilerException("parameter type mismatch $call")
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val paramtypes = func.parameters.map { it.possibleDatatypes }
for(x in argtypes.zip(paramtypes)) {
if(x.first !in x.second)
throw CompilerException("parameter type mismatch $call")
}
}
else -> {}
}
}
}

View File

@ -3,13 +3,14 @@ package prog8.ast.statements
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
sealed class Statement : Node {
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
@ -28,8 +29,6 @@ sealed class Statement : Node {
return scope.joinToString(".")
}
abstract val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
@ -41,32 +40,37 @@ sealed class Statement : Node {
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override val expensiveToInline = false
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it ===node }
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Block(name=$name, address=$address, ${statements.size} statements)"
@ -77,15 +81,15 @@ class Block(override val name: String,
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
args.forEach{it.linkParents(this)}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
@ -94,18 +98,19 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
data class Label(val name: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Label(name=$name, pos=$position)"
@ -114,15 +119,20 @@ data class Label(val name: String, override val position: Position) : Statement(
open class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = value!=null && value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
value?.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
value = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Return($value, pos=$position)"
@ -130,36 +140,36 @@ open class Return(var value: Expression?, override val position: Position) : Sta
}
class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class Break(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -170,7 +180,8 @@ enum class ZeropageWish {
NOT_IN_ZEROPAGE
}
class VarDecl(val type: VarDeclType,
open class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
@ -186,32 +197,33 @@ class VarDecl(val type: VarDeclType,
var structHasBeenFlattened = false // set later
private set
override val expensiveToInline
get() = value!=null && value !is NumericLiteralValue
// prefix for literal values that are turned into a variable on the heap
companion object {
private var autoHeapValueSequenceNumber = 0
fun createAuto(string: StringLiteralValue): VarDecl {
if(string.heapId==null)
throw FatalAstException("can only create autovar for a string that has a heapid $string")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return VarDecl(VarDeclType.VAR, string.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
if(array.heapId==null)
throw FatalAstException("can only create autovar for an array that has a heapid $array")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type)
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
}
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
@ -240,34 +252,25 @@ class VarDecl(val type: VarDeclType,
}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
value = replacement
replacement.parent = this
}
val scopedname: String by lazy { makeScopedName(name) }
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
@ -286,6 +289,11 @@ class VarDecl(val type: VarDeclType,
}
}
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.NOT_IN_ZEROPAGE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node {
override lateinit var parent: Node
@ -294,19 +302,20 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
index.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
}
}
fun accept(visitor: IAstModifyingVisitor) {
index = index.accept(visitor)
}
fun accept(visitor: IAstVisitor) {
index.accept(visitor)
}
fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
@ -315,10 +324,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
}
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
@ -326,21 +333,47 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
value.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===target -> target = replacement as AssignTarget
node===value -> value = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
fun asDesugaredNonaugmented(): Assignment {
val augmented = aug_op ?: return this
val leftOperand: Expression =
when {
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!!
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
else -> throw FatalAstException("strange this")
}
val assignment =
if(augmented=="setvalue") {
Assignment(target, null, value, position)
} else {
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
Assignment(target, null, expression, position)
}
assignment.linkParents(parent)
return assignment
}
}
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
: Assignment(target, aug_op, value, position)
data class AssignTarget(val register: Register?,
var identifier: IdentifierReference?,
data class AssignTarget(var identifier: IdentifierReference?,
var arrayindexed: ArrayIndexedExpression?,
val memoryAddress: DirectMemoryWrite?,
override val position: Position) : Node {
@ -353,25 +386,30 @@ data class AssignTarget(val register: Register?,
memoryAddress?.linkParents(this)
}
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun fromExpr(expr: Expression): AssignTarget {
return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(register!=null)
return InferredTypes.knownFor(DataType.UBYTE)
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
@ -389,8 +427,13 @@ data class AssignTarget(val register: Register?,
infix fun isSameAs(value: Expression): Boolean {
return when {
this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register
this.memoryAddress!=null -> {
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
if(value is DirectMemoryRead)
this.memoryAddress.addressExpression isSameAs value.addressExpression
else
false
}
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
@ -404,8 +447,6 @@ data class AssignTarget(val register: Register?,
fun isSameAs(other: AssignTarget, program: Program): Boolean {
if(this===other)
return true
if(this.register!=null && other.register!=null)
return this.register==other.register
if(this.identifier!=null && other.identifier!=null)
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
@ -424,8 +465,6 @@ data class AssignTarget(val register: Register?,
}
fun isNotMemory(namespace: INameScope): Boolean {
if(this.register!=null)
return true
if(this.memoryAddress!=null)
return false
if(this.arrayindexed!=null) {
@ -444,15 +483,20 @@ data class AssignTarget(val register: Register?,
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AssignTarget && node===target)
target = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
@ -464,15 +508,15 @@ class Jump(val address: Int?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
@ -480,20 +524,29 @@ class Jump(val address: Int?,
}
class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<Expression>,
override var args: MutableList<Expression>,
val void: Boolean,
override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node
override val expensiveToInline
get() = arglist.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
args.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target = replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "FunctionCallStatement(target=$target, pos=$position)"
@ -502,22 +555,20 @@ class FunctionCallStatement(override var target: IdentifierReference,
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
companion object {
private var sequenceNumber = 1
@ -533,28 +584,27 @@ class AnonymousScope(override var statements: MutableList<Statement>,
statements.forEach { it.linkParents(this) }
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class NopStatement(override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object {
fun insteadOf(stmt: Statement): NopStatement {
val nop = NopStatement(stmt.position)
nop.parent = stmt.parent
return nop
}
}
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
// the subroutine class covers both the normal user-defined subroutines,
@ -565,20 +615,14 @@ class Subroutine(override val name: String,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>,
val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override lateinit var parent: Node
val calledBy = mutableListOf<Node>()
val calls = mutableSetOf<Subroutine>()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
@ -587,13 +631,22 @@ class Subroutine(override val name: String,
statements.forEach { it.linkParents(this) }
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun amountOfRtsInAsm(): Int = statements
.asSequence()
.filter { it is InlineAssembly }
@ -601,6 +654,7 @@ class Subroutine(override val name: String,
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
}
open class SubroutineParameter(val name: String,
val type: DataType,
override val position: Position) : Node {
@ -609,6 +663,10 @@ open class SubroutineParameter(val name: String,
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace anything in a subroutineparameter node")
}
}
class IfStatement(var condition: Expression,
@ -616,8 +674,6 @@ class IfStatement(var condition: Expression,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
@ -626,8 +682,19 @@ class IfStatement(var condition: Expression,
elsepart.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
node===truepart -> truepart = replacement as AnonymousScope
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class BranchStatement(var condition: BranchCondition,
@ -635,8 +702,6 @@ class BranchStatement(var condition: BranchCondition,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) {
this.parent = parent
@ -644,38 +709,57 @@ class BranchStatement(var condition: BranchCondition,
elsepart.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===truepart -> truepart = replacement as AnonymousScope
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class ForLoop(val loopRegister: Register?,
var loopVar: IdentifierReference?,
class ForLoop(var loopVar: IdentifierReference,
var iterable: Expression,
var body: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent=parent
loopVar?.linkParents(this)
loopVar.linkParents(this)
iterable.linkParents(this)
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===loopVar -> loopVar = replacement as IdentifierReference
node===iterable -> iterable = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
}
fun loopVarDt(program: Program) = loopVar.inferType(program)
}
class WhileLoop(var condition: Expression,
var body: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@ -683,15 +767,45 @@ class WhileLoop(var condition: Expression,
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class RepeatLoop(var body: AnonymousScope,
var untilCondition: Expression,
override val position: Position) : Statement() {
class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
iterations?.linkParents(this)
body.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===iterations -> iterations = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class UntilLoop(var body: AnonymousScope,
var untilCondition: Expression,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
@ -699,15 +813,23 @@ class RepeatLoop(var body: AnonymousScope,
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===untilCondition -> untilCondition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenStatement(var condition: Expression,
var choices: MutableList<WhenChoice>,
override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
@ -715,6 +837,16 @@ class WhenStatement(var condition: Expression,
choices.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===condition)
condition = replacement as Expression
else {
val idx = choices.withIndex().find { it.value===node }!!.index
choices[idx] = replacement as WhenChoice
}
replacement.parent = this
}
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
// only gives sensible results when the choices are all valid (constant integers)
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
@ -733,7 +865,7 @@ class WhenStatement(var condition: Expression,
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
@ -747,12 +879,18 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
}
override fun toString(): String {
return "Choice($values at $position)"
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -761,18 +899,24 @@ class StructDecl(override val name: String,
override val position: Position): Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
this.statements.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
val numberOfElements: Int
get() = this.statements.size
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun nameOfFirstMember() = (statements.first() as VarDecl).name
}
@ -785,11 +929,16 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
this.addressExpression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}

View File

@ -0,0 +1,3 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,106 @@
package prog8.compiler
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
}
return mods
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
else if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
return noModifications
}
}

View File

@ -1,13 +1,8 @@
package prog8.compiler
import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.StringDatatypes
import prog8.ast.expressions.AddressOf
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import java.util.*
import kotlin.math.abs
enum class OutputType {
@ -28,8 +23,6 @@ enum class ZeropageType {
DONTUSE
}
data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?)
data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
@ -73,77 +66,3 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}
class HeapValues {
data class HeapValue(val type: DataType, val str: String?, val array: Array<IntegerOrAddressOf>?, val doubleArray: DoubleArray?) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HeapValue
return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray)
}
override fun hashCode(): Int = Objects.hash(str, array, doubleArray)
val arraysize: Int = array?.size ?: doubleArray?.size ?: 0
}
private val heap = mutableMapOf<Int, HeapValue>()
private var heapId = 1
fun size(): Int = heap.size
fun addString(type: DataType, str: String): Int {
if (str.length > 255)
throw IllegalArgumentException("string length must be 0-255")
// strings are 'interned' and shared if they're the isSameAs
val value = HeapValue(type, str, null, null)
val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull()
if(existing!=null)
return existing
val newId = heapId++
heap[newId] = value
return newId
}
fun addIntegerArray(type: DataType, array: Array<IntegerOrAddressOf>): Int {
// arrays are never shared, don't check for existing
if(type !in ArrayDatatypes)
throw CompilerException("wrong array type")
val newId = heapId++
heap[newId] = HeapValue(type, null, array, null)
return newId
}
fun addDoublesArray(darray: DoubleArray): Int {
// arrays are never shared, don't check for existing
val newId = heapId++
heap[newId] = HeapValue(DataType.ARRAY_F, null, null, darray)
return newId
}
fun update(heapId: Int, str: String) {
val oldVal = heap[heapId] ?: throw IllegalArgumentException("heapId not found in heap")
if(oldVal.type in StringDatatypes) {
if (oldVal.str!!.length != str.length)
throw IllegalArgumentException("heap string length mismatch")
heap[heapId] = oldVal.copy(str = str)
}
else throw IllegalArgumentException("heap data type mismatch")
}
fun update(heapId: Int, heapval: HeapValue) {
if(heapId !in heap)
throw IllegalArgumentException("heapId not found in heap")
heap[heapId] = heapval
}
fun get(heapId: Int): HeapValue {
return heap[heapId] ?:
throw IllegalArgumentException("heapId $heapId not found in heap")
}
fun allEntries() = heap.entries
}

View File

@ -4,14 +4,13 @@ import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.CompilationTarget
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.measureTimeMillis
@ -25,90 +24,33 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean): CompilationResult {
writeAssembly: Boolean,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
var programName: String? = null
var importedFiles: List<Path> = emptyList()
var success=false
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts()
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
if (optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
success = true
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
@ -131,16 +73,35 @@ fun compileProgram(filepath: Path,
System.out.flush()
throw x
}
return CompilationResult(success, programAst, programName ?: "", importedFiles)
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Parsing...")
val importer = ModuleImporter(errors)
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importer.importLibraryModule(programAst, "c64lib")
importer.importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
@ -177,3 +138,79 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
zpType, zpReserved, floatsEnabled
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
errors.handle()
if (optsDone1 + optsDone2 == 0)
break
}
val remover = UnusedCodeRemover()
remover.visit(programAst)
remover.applyModifications()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.transformAssignments(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
errors.handle()
programAst.verifyFunctionArgTypes()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
val assembly = CompilationTarget.asmGenerator(
programAst,
errors,
zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
DataType.FLOAT -> {
if (options.floats) {
if(position!=null)
printWarning("allocated a large value (float) in zeropage", position)
errors.warn("allocated a large value (float) in zeropage", position)
else
printWarning("$scopedname: allocated a large value (float) in zeropage")
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
5
} else throw CompilerException("floating point option not enabled")
}

View File

@ -0,0 +1,18 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import java.nio.file.Path
internal interface CompilationTarget {
companion object {
lateinit var name: String
lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}

View File

@ -0,0 +1,14 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)
}

View File

@ -0,0 +1,15 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val opcodeNames: Set<String>
fun getZeropage(compilerOptions: CompilationOptions): Zeropage
}

View File

@ -2,70 +2,72 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import java.io.File
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(val name: String) {
private val assemblyFile = "$name.asm"
private val viceMonListFile = "$name.vice-mon-list"
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
companion object {
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
val opcodeNames = 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")
}
fun assemble(options: CompilationOptions) {
override fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when(options.output) {
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating C-64 prg.")
"$name.prg"
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary.")
"$name.bin"
binFile
}
}
command.addAll(listOf("--output", outFile, assemblyFile))
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if(result!=0) {
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
if(pattern.matchEntire(line)==null)
it.write(line+"\n")
}
}
}
private fun generateBreakpointList() {
// builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in File(viceMonListFile).readLines()) {
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
for (line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line)
if(match!=null)
breakpoints.add("break \$" + match.groupValues[1])
if (match != null)
breakpoints.add("break \$" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del")
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}

View File

@ -4,18 +4,16 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import prog8.compiler.target.IMachineDefinition
import kotlin.math.absoluteValue
import kotlin.math.pow
object MachineDefinition {
object C64MachineDefinition: IMachineDefinition {
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
@ -30,6 +28,19 @@ object MachineDefinition {
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions)
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = 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")
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
@ -110,8 +121,6 @@ object MachineDefinition {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
@ -165,91 +174,4 @@ object MachineDefinition {
return if (sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
val nopixel = Color(0, 0, 0, 0).rgb
for (y in 0 until transparent.height) {
for (x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if (col == black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
}
return charImages.toTypedArray()
}
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % colorPalette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if (chars != null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0, 0, 0, 0).rgb
val rgb = colorPalette[colorIdx.toInt()].rgb
for (c in normalChars) {
val colored = c.copy()
for (y in 0 until colored.height)
for (x in 0 until colored.width) {
if (colored.getRGB(x, y) != transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}

View File

@ -1058,7 +1058,7 @@ object Petscii {
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it'")
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
}
}
}
@ -1076,7 +1076,7 @@ object Petscii {
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it'")
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
}
}
}

View File

@ -1,48 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
return checkResult
}
override fun visit(program: Program) {
varsToMove.clear()
super.visit(program)
for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
conflicts = true
}
}
if (!conflicts) {
decls.forEach { scope.remove(it) }
sub.statements.addAll(0, decls)
decls.forEach { it.parent = sub }
}
}
}
override fun visit(scope: AnonymousScope): Statement {
val scope2 = super.visit(scope) as AnonymousScope
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
varsToMove[scope2] = vardecls
return scope2
}
}

View File

@ -1,5 +1,6 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape
@ -7,25 +8,29 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
import java.io.File
import prog8.functions.FSignature
import java.math.RoundingMode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.math.absoluteValue
internal class AssemblyError(msg: String) : RuntimeException(msg)
internal class AsmGen(val program: Program,
val options: CompilationOptions,
val zeropage: Zeropage) {
internal class AsmGen(private val program: Program,
private val errors: ErrorReporter,
private val zeropage: Zeropage,
private val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
@ -35,12 +40,13 @@ internal class AsmGen(val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, errors, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = Stack<String>()
internal val loopContinueLabels = Stack<String>()
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal fun compileToAssembly(optimize: Boolean): AssemblyProgram {
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
loopContinueLabels.clear()
@ -62,17 +68,18 @@ internal class AsmGen(val program: Program,
}
}
File("${program.name}.asm").printWriter().use {
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name)
return AssemblyProgram(program.name, outputDir)
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${Date()}")
out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
@ -80,7 +87,7 @@ internal class AsmGen(val program: Program,
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
C64MachineDefinition.BASIC_LOAD_ADDRESS else C64MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
@ -88,16 +95,20 @@ internal class AsmGen(val program: Program,
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.actualLoadAddress.toHex()}")
val year = Calendar.getInstance().get(Calendar.YEAR)
val year = LocalDate.now().year
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
@ -117,11 +128,10 @@ internal class AsmGen(val program: Program,
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for (block in program.allBlocks()) {
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
if(initVarsSub!=null)
out(" jsr ${block.name}.$initvarsSubName")
out(" ; initialize the variables in each block that has globals")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out(" clc")
@ -134,14 +144,13 @@ internal class AsmGen(val program: Program,
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key)
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(flt.key)
val floatFill = makeFloatFill(mflpt5)
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
@ -168,6 +177,21 @@ internal class AsmGen(val program: Program,
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
// if any global vars need to be initialized, generate a subroutine that does this
// it will be called from program init.
if(block in blockLevelVarInits) {
out("prog8_init_vars\t.proc\n")
blockLevelVarInits.getValue(block).forEach { decl ->
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
val target = AssignTarget(IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
val assign = Assignment(target, null, decl.value!!, decl.position)
assign.linkParents(decl.parent)
assignmentAsmGen.translate(assign)
}
out(" rts\n .pend")
}
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
@ -175,7 +199,7 @@ internal class AsmGen(val program: Program,
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
}
private fun outputSourceLine(node: Node) {
@ -194,7 +218,7 @@ internal class AsmGen(val program: Program,
} else assemblyLines.add(fragment)
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
private fun makeFloatFill(flt: C64MachineDefinition.Mflpt5): String {
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
@ -203,18 +227,9 @@ internal class AsmGen(val program: Program,
return "$b0, $b1, $b2, $b3, $b4"
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
private fun encode(str: String, altEncoding: Boolean): List<Short> {
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return bytes.plus(0)
}
private fun zeropagevars2asm(statements: List<Statement>) {
@ -222,7 +237,7 @@ internal class AsmGen(val program: Program,
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
for(variable in variables) {
// should NOT allocate subroutine parameters on the zero page
val fullName = variable.scopedname
val fullName = variable.makeScopedName(variable.name)
val zpVar = allocatedZeropageVariables[fullName]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
@ -231,7 +246,9 @@ internal class AsmGen(val program: Program,
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val address = zeropage.allocate(fullName, variable.datatype, null)
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
@ -251,10 +268,9 @@ internal class AsmGen(val program: Program,
DataType.WORD -> out("${decl.name}\t.sint 0")
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
DataType.STRUCT -> {} // is flattened
DataType.STR, DataType.STR_S -> {
val string = (decl.value as StringLiteralValue).value
val encoded = encodeStr(string, decl.datatype)
outputStringvar(decl, encoded)
DataType.STR -> {
val str = decl.value as StringLiteralValue
outputStringvar(decl, encode(str.value, str.altEncoding))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
@ -297,10 +313,17 @@ internal class AsmGen(val program: Program,
}
}
DataType.ARRAY_F -> {
val array = (decl.value as ArrayLiteralValue).value
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no init value, use zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number))
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
}
out(decl.name)
for (f in array.zip(floatFills))
@ -337,8 +360,11 @@ internal class AsmGen(val program: Program,
// special treatment for string types: merge strings that are identical
val encodedstringVars = normalVars
.filter {it.datatype in StringDatatypes }
.map { it to encodeStr((it.value as StringLiteralValue).value, it.datatype) }
.filter {it.datatype == DataType.STR }
.map {
val str = it.value as StringLiteralValue
it to encode(str.value, str.altEncoding)
}
.groupBy({it.second}, {it.first})
for((encoded, variables) in encodedstringVars) {
variables.dropLast(1).forEach { out(it.name) }
@ -347,8 +373,8 @@ internal class AsmGen(val program: Program,
}
// non-string variables
normalVars.filter{ it.datatype !in StringDatatypes}.sortedBy { it.datatype }.forEach {
if(it.scopedname !in allocatedZeropageVariables)
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
vardecl2asm(it)
}
}
@ -362,15 +388,22 @@ internal class AsmGen(val program: Program,
}
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
return when (decl.datatype) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
"$"+number.toString(16).padStart(2, '0')
}
decl.datatype== DataType.ARRAY_UW -> array.map {
DataType.ARRAY_UW -> array.map {
if(it is NumericLiteralValue) {
"$" + it.number.toInt().toString(16).padStart(4, '0')
} else {
@ -382,17 +415,22 @@ internal class AsmGen(val program: Program,
}
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
return when (decl.datatype) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(2, '0')
"$$hexnum"
"$"+number.toString(16).padStart(2, '0')
}
decl.datatype == DataType.ARRAY_B ->
DataType.ARRAY_B ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
@ -402,12 +440,11 @@ internal class AsmGen(val program: Program,
else
"-$$hexnum"
}
decl.datatype== DataType.ARRAY_UW -> array.map {
DataType.ARRAY_UW -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(4, '0')
"$$hexnum"
"$" + number.toString(16).padStart(4, '0')
}
decl.datatype== DataType.ARRAY_W -> array.map {
DataType.ARRAY_W -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@ -421,7 +458,7 @@ internal class AsmGen(val program: Program,
internal fun getFloatConst(number: Double): String {
// try to match the ROM float constants to save memory
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number)
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_ZERO"
@ -507,7 +544,7 @@ internal class AsmGen(val program: Program,
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
val variablename = asmIdentifierName(variable)
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
out(" tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
out(" asl a | tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | lda $variablename+1,y | sta $ESTACK_HI_HEX,x | dex")
@ -525,19 +562,19 @@ internal class AsmGen(val program: Program,
}
}
internal fun saveRegister(register: Register) {
internal fun saveRegister(register: CpuRegister) {
when(register) {
Register.A -> out(" pha")
Register.X -> out(" txa | pha")
Register.Y -> out(" tya | pha")
CpuRegister.A -> out(" pha")
CpuRegister.X -> out(" txa | pha")
CpuRegister.Y -> out(" tya | pha")
}
}
internal fun restoreRegister(register: Register) {
internal fun restoreRegister(register: CpuRegister) {
when(register) {
Register.A -> out(" pla")
Register.X -> out(" pla | tax")
Register.Y -> out(" pla | tay")
CpuRegister.A -> out(" pla")
CpuRegister.X -> out(" pla | tax")
CpuRegister.Y -> out(" pla | tay")
}
}
@ -569,7 +606,9 @@ internal class AsmGen(val program: Program,
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is VarDecl, is StructDecl, is NopStatement -> {}
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
is VarDecl -> translate(stmt)
is StructDecl, is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
@ -603,10 +642,12 @@ internal class AsmGen(val program: Program,
is Break -> out(" jmp ${loopEndLabels.peek()}")
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is UntilLoop -> translate(stmt)
is WhenStatement -> translate(stmt)
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
is AnonymousScope -> translate(stmt)
is Block -> throw AssemblyError("block should have been handled elsewhere")
else -> throw AssemblyError("missing asm translation for $stmt")
}
}
@ -632,13 +673,114 @@ internal class AsmGen(val program: Program,
}
}
private fun translate(stmt: RepeatLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
val counterLabel = makeLabel("repeatcounter")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
when (stmt.iterations) {
null -> {
// endless loop
out(repeatLabel)
translate(stmt.body)
out(" jmp $repeatLabel")
out(endLabel)
}
is NumericLiteralValue -> {
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
if(iterations<0 || iterations > 65536)
throw AssemblyError("invalid number of iterations")
when {
iterations == 0 -> {}
iterations <= 255 -> {
out(" lda #${iterations}")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
}
else -> {
out(" lda #<${iterations} | ldy #>${iterations}")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
}
}
}
is IdentifierReference -> {
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl
val name = asmIdentifierName(stmt.iterations as IdentifierReference)
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
out(" lda $name")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
}
DataType.UWORD, DataType.WORD -> {
out(" lda $name | ldy $name+1")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
}
}
else -> {
translateExpression(stmt.iterations!!)
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
when (dt) {
in ByteDatatypes -> {
out(" inx | lda ${ESTACK_LO_HEX},x")
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
}
in WordDatatypes -> {
out(" inx | lda ${ESTACK_LO_HEX},x | ldy ${ESTACK_HI_HEX},x")
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
}
else -> throw AssemblyError("invalid loop expression datatype $dt")
}
}
}
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun repeatWordCountInAY(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A/Y must have been loaded with the number of iterations already!
out("""
sta $counterLabel
sty $counterLabel+1
$repeatLabel lda $counterLabel
bne +
lda $counterLabel+1
beq $endLabel
+ lda $counterLabel
bne +
dec $counterLabel+1
+ dec $counterLabel
""")
translate(body)
out("""
jmp $repeatLabel
$counterLabel .word 0
$endLabel""")
}
private fun repeatByteCountInA(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A must have been loaded with the number of iterations already!
out("""
sta $counterLabel
$repeatLabel lda $counterLabel
beq $endLabel
dec $counterLabel""")
translate(body)
out("""
jmp $repeatLabel
$counterLabel .byte 0
$endLabel""")
}
private fun translate(stmt: WhileLoop) {
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(whileLabel)
out(whileLabel)
// TODO optimize for the simple cases, can we avoid stack use?
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
@ -661,13 +803,12 @@ internal class AsmGen(val program: Program,
loopContinueLabels.pop()
}
private fun translate(stmt: RepeatLoop) {
private fun translate(stmt: UntilLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
out(repeatLabel)
// TODO optimize this for the simple cases, can we avoid stack use?
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
@ -718,7 +859,7 @@ internal class AsmGen(val program: Program,
bne +
cpy #>${value.toHex()}
beq $choiceLabel
+
+
""")
}
}
@ -733,7 +874,7 @@ internal class AsmGen(val program: Program,
}
private fun translate(stmt: Label) {
out(stmt.name)
out("_${stmt.name}") // underscore prefix to make sure it's a local label
}
private fun translate(scope: AnonymousScope) {
@ -772,6 +913,30 @@ internal class AsmGen(val program: Program,
}
}
private fun translate(stmt: VarDecl) {
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
// generate an assignment statement to (re)initialize the variable's value.
// if the vardecl is not in a subroutine however, we have to initialize it globally.
if(stmt.definingSubroutine()==null) {
val block = stmt.definingBlock()
var inits = blockLevelVarInits[block]
if(inits==null) {
inits = mutableSetOf()
blockLevelVarInits[block] = inits
}
inits.add(stmt)
} else {
val next = (stmt.parent as INameScope).nextSibling(stmt)
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
val target = AssignTarget(IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
val assign = Assignment(target, null, stmt.value!!, stmt.position)
assign.linkParents(stmt.parent)
translate(assign)
}
}
}
}
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {
@ -802,7 +967,14 @@ internal class AsmGen(val program: Program,
private fun getJumpTarget(jmp: Jump): String {
return when {
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
jmp.identifier!=null -> {
val target = jmp.identifier.targetStatement(program.namespace)
val asmName = asmIdentifierName(jmp.identifier)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
jmp.generatedLabel!=null -> jmp.generatedLabel
jmp.address!=null -> jmp.address.toHex()
else -> "????"
@ -820,20 +992,13 @@ internal class AsmGen(val program: Program,
}
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
val index = expr.arrayspec.index
when (index) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> {}
Register.X -> out(" txa")
Register.Y -> out(" tya")
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
// TODO optimize more cases
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
@ -841,10 +1006,25 @@ internal class AsmGen(val program: Program,
}
}
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" ldy $indexName")
}
// TODO optimize more cases, see translateArrayIndexIntoA
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | ldy $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =
@ -871,9 +1051,12 @@ internal class AsmGen(val program: Program,
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromFloatVariable(target, variable)
fun assignFromRegister(target: AssignTarget, register: Register) =
fun assignFromRegister(target: AssignTarget, register: CpuRegister) =
assignmentAsmGen.assignFromRegister(target, register)
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) =
assignmentAsmGen.assignToRegister(reg, value, identifier)
}

View File

@ -1,7 +1,7 @@
package prog8.compiler.target.c64.codegen
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -13,43 +13,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
var linesByFour = getLinesBy(lines, 4)
var removeLines = optimizeUselessStackByteWrites(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
var mods = optimizeUselessStackByteWrites(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeIncDec(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeIncDec(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeCmpSequence(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeCmpSequence(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeStoreLoadSame(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeStoreLoadSame(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods= optimizeJsrRts(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
removeLines = optimizeSameAssignments(linesByFourteen)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeSameAssignments(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
@ -59,7 +61,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
return numberOfOptimizations
}
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
if(modification.remove)
lines.removeAt(modification.lineIndex)
else
lines[modification.lineIndex] = modification.replacement!!
}
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
@ -68,42 +85,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
removeLines.add(lines[3].index) // remove the second lda
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
}
}
return removeLines
return mods
}
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies.
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[1].index)
removeLines.add(lines[2].index)
removeLines.add(lines[3].index)
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return removeLines
return mods
}
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and transform the Ast, and never even create the inefficient asm in the first place...
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for (pair in linesByFourteen) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -122,8 +139,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
removeLines.add(pair[4].index)
removeLines.add(pair[5].index)
mods.add(Modification(pair[4].index, true, null))
mods.add(Modification(pair[5].index, true, null))
}
}
@ -132,7 +149,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
removeLines.add(pair[2].index)
mods.add(Modification(pair[2].index, true, null))
}
}
@ -151,24 +168,20 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
removeLines.add(pair[7].index)
removeLines.add(pair[8].index)
removeLines.add(pair[9].index)
removeLines.add(pair[10].index)
mods.add(Modification(pair[7].index, true, null))
mods.add(Modification(pair[8].index, true, null))
mods.add(Modification(pair[9].index, true, null))
mods.add(Modification(pair[10].index, true, null))
}
}
}
}
return removeLines
return mods
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -186,26 +199,40 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
if (firstLoc == secondLoc) {
removeLines.add(pair[1].index)
mods.add(Modification(pair[1].index, true, null))
}
}
}
return removeLines
return mods
}
private fun optimizeIncDec(linesByTwo: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val removeLines = mutableListOf<Int>()
for (pair in linesByTwo) {
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
removeLines.add(pair[0].index)
removeLines.add(pair[1].index)
mods.add(Modification(pair[0].index, true, null))
mods.add(Modification(pair[1].index, true, null))
}
}
return removeLines
return mods
}
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
mods += Modification(pair[1].index, true, null)
}
}
return mods
}

View File

@ -2,33 +2,29 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.base.WordDatatypes
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import prog8.functions.FunctionSignature
import prog8.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last()
if (discardResult) {
if (func.pure)
@ -38,343 +34,566 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
when (functionName) {
"msb" -> {
val arg = fcall.arglist.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
}
}
"mkword" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
}
"abs" -> {
translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
else -> throw AssemblyError("weird type")
}
}
"swap" -> {
val first = fcall.arglist[0]
val second = fcall.arglist[1]
asmgen.translateExpression(first)
asmgen.translateExpression(second)
// pop in reverse order
val firstTarget = AssignTarget.fromExpr(first)
val secondTarget = AssignTarget.fromExpr(second)
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
}
"strlen" -> {
outputPushAddressOfIdentifier(fcall.arglist[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
"min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"any", "all" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sgn" -> {
translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)
when(dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
"msb" -> funcMsb(fcall)
"mkword" -> funcMkword(fcall, func)
"abs" -> funcAbs(fcall, func)
"swap" -> funcSwap(fcall)
"strlen" -> funcStrlen(fcall)
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
"any", "all" -> funcAnyAll(fcall, functionName)
"sgn" -> funcSgn(fcall, func)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" jsr c64flt.func_$functionName")
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
"lsl" -> funcLsl(fcall)
"lsr" -> funcLsr(fcall)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"rsave" -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
}
/*
TODO this was the old code for bit rotations:
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
*/
"lsl" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay")
}
}
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsl memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsl byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
when (what) {
is ArrayIndexedExpression -> TODO("lsl sbyte $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"lsr" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay")
}
}
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsr memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsr byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when (what) {
is ArrayIndexedExpression -> TODO("lsr sbyte $what")
is DirectMemoryRead -> TODO("lsr sbyte $what")
is RegisterExpr -> TODO("lsr sbyte $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | asl a | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> TODO("lsr uword $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when (what) {
is ArrayIndexedExpression -> TODO("lsr sword $what")
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"rol" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
TODO("rol ubyte")
}
DataType.UWORD -> {
TODO("rol uword")
}
else -> throw AssemblyError("weird type")
}
}
"rol2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
TODO("rol2 ubyte")
}
DataType.UWORD -> {
TODO("rol2 uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
TODO("ror ubyte")
}
DataType.UWORD -> {
TODO("ror uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
TODO("ror2 ubyte")
}
DataType.UWORD -> {
TODO("ror2 uword")
}
else -> throw AssemblyError("weird type")
}
}
"sort" -> {
val variable = fcall.arglist.single()
if(variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1+1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if(decl.datatype==DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1+1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if(decl.datatype==DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
}
DataType.ARRAY_F -> TODO("sort floats (consider another solution if possible - this will be very slow, if ever implemented)")
else -> throw AssemblyError("weird type")
}
}
else
throw AssemblyError("weird type")
}
"reverse" -> {
val variable = fcall.arglist.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_b
""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_w
""")
}
DataType.ARRAY_F -> TODO("reverse floats (consider another solution if possible - this will be quite slow, if ever implemented)")
else -> throw AssemblyError("weird type")
}
}
"rrestore" -> {
// restore all registers and cpu status flag
asmgen.out(" pla | tay | pla | tax | pla | plp")
}
"clear_carry" -> asmgen.out(" clc")
"set_carry" -> asmgen.out(" sec")
"clear_irqd" -> asmgen.out(" cli")
"set_irqd" -> asmgen.out(" sei")
else -> {
translateFunctionArguments(fcall.arglist, func)
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
}
}
}
private fun funcReverse(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_b
""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_w
""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_f
""")
}
else -> throw AssemblyError("weird type")
}
}
}
private fun funcSort(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
else -> throw AssemblyError("weird type")
}
} else
throw AssemblyError("weird type")
}
private fun funcRor2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRor(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" ror ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ ror ${'$'}ffff ; modified
""")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRol2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRol(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" rol ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ rol ${'$'}ffff ; modified
""")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" rol $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" rol $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcLsr(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lsr ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ lsr ${'$'}ffff ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_ub")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_b")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | asl a | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcLsl(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
when (what) {
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" asl ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ asl ${'$'}ffff ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_b")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr c64flt.func_$functionName")
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcAnyAll(fcall: IFunctionCall, functionName: String) {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcStrlen(fcall: IFunctionCall) {
outputPushAddressOfIdentifier(fcall.args[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0]
val second = fcall.args[1]
if(first is IdentifierReference && second is IdentifierReference) {
val firstName = asmgen.asmIdentifierName(first)
val secondName = asmgen.asmIdentifierName(second)
val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
return
}
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
asmgen.out("""
ldy $firstName
lda $secondName
sta $firstName
sty $secondName
ldy $firstName+1
lda $secondName+1
sta $firstName+1
sty $secondName+1
""")
return
}
if(dt.istype(DataType.FLOAT)) {
asmgen.out("""
lda #<$firstName
sta ${C64Zeropage.SCRATCH_W1}
lda #>$firstName
sta ${C64Zeropage.SCRATCH_W1+1}
lda #<$secondName
sta ${C64Zeropage.SCRATCH_W2}
lda #>$secondName
sta ${C64Zeropage.SCRATCH_W2+1}
jsr c64flt.swap_floats
""")
return
}
}
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
throw AssemblyError("no asm generation for swap funccall $fcall")
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
else -> throw AssemblyError("weird type")
}
}
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
}
private fun funcMsb(fcall: IFunctionCall) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg)
@ -402,7 +621,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
""")
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
args.forEach {
asmgen.translateExpression(it)
}

View File

@ -3,7 +3,13 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue
@ -19,11 +25,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> translateExpression(expression)
is NumericLiteralValue -> translateExpression(expression)
is RegisterExpr -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateExpression(expression)
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
@ -34,18 +38,29 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!!
asmgen.translateFunctionCall(expression)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
if (!reg.stack) {
// result value in cpu or status registers, put it on the stack
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.AY -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | tya | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
}
}
}
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
@ -60,7 +75,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UBYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta $ESTACK_HI_PLUS1_HEX,x")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
@ -69,7 +84,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.BYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | ${asmgen.signExtendAtoMsb("${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")}")
DataType.UWORD, DataType.WORD -> asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | ${asmgen.signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
@ -104,42 +119,50 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("weird type")
}
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmIdentifierName(expr.identifier)
asmgen.out(" lda #<$name | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$name | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
asmgen.out(" lda #<$name | sta $ESTACK_LO_HEX,x | lda #>$name | sta $ESTACK_HI_HEX,x | dex")
}
private fun translateExpression(expr: DirectMemoryRead) {
when(expr.addressExpression) {
is NumericLiteralValue -> {
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
asmgen.out(" lda ${address.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
}
is IdentifierReference -> {
// the identifier is a pointer variable, so read the value from the address in it
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out("""
lda $sourceName
sta (+) +1
lda $sourceName+1
sta (+) +2
+ lda ${'$'}ffff ; modified
sta $ESTACK_LO_HEX,x
dex""")
}
else -> {
translateExpression(expr.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address")
asmgen.out(" sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x")
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
}
private fun translateExpression(expr: NumericLiteralValue) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
lda #<${expr.number.toHex()}
sta ${MachineDefinition.ESTACK_LO_HEX},x
sta $ESTACK_LO_HEX,x
lda #>${expr.number.toHex()}
sta ${MachineDefinition.ESTACK_HI_HEX},x
sta $ESTACK_HI_HEX,x
dex
""")
DataType.FLOAT -> {
@ -150,27 +173,21 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
Register.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
}
}
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmIdentifierName(expr)
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
}
DataType.UWORD, DataType.WORD, in ArrayDatatypes, in StringDatatypes -> {
// (for arrays and strings, push their address)
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $varname+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
}
in IterableDatatypes -> {
asmgen.out(" lda #<$varname | sta $ESTACK_LO_HEX,x | lda #>$varname | sta $ESTACK_HI_HEX,x | dex")
}
else -> throw AssemblyError("stack push weird variable type $expr")
}
}
@ -194,10 +211,46 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
when (leftDt) {
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.BYTE -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.WORD -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.UBYTE -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
repeat(amount) { asmgen.out(" lsr a") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
DataType.BYTE -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | sta ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}")
repeat(amount) { asmgen.out(" asl a | ror ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} | lda ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
DataType.UWORD -> {
var left = amount
while(left>=7) {
asmgen.out(" jsr math.shift_right_uw_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_right_uw_$left")
}
DataType.WORD -> {
var left = amount
while(left>=7) {
asmgen.out(" jsr math.shift_right_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_right_w_$left")
}
else -> throw AssemblyError("weird type")
}
return
@ -206,10 +259,26 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// bit-shifts are always by a constant number (for now)
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
if (leftDt in ByteDatatypes)
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
else
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | rol ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") }
if (leftDt in ByteDatatypes) {
if(amount<=2)
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
repeat(amount) { asmgen.out(" asl a") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
else {
var left=amount
while(left>=7) {
asmgen.out(" jsr math.shift_left_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_left_w_$left")
}
return
}
"*" -> {
@ -217,8 +286,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount in powersOfTwo)
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
when(rightDt) {
DataType.UBYTE -> {
if(amount in optimizedByteMultiplications) {
@ -268,8 +335,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if(leftDt!=rightDt)
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
@ -295,9 +364,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sta $ESTACK_LO_PLUS1_HEX,x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
@ -324,10 +393,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
@ -351,18 +420,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sta $ESTACK_LO_PLUS1_HEX,x
""")
"-" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sta $ESTACK_LO_PLUS1_HEX,x
""")
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")

View File

@ -2,16 +2,21 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import kotlin.math.absoluteValue
// todo choose more efficient comparisons to avoid needless lda's
// todo optimize common case when step == 2 or -2
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -25,15 +30,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
} else {
if (range.isEmpty())
throw AssemblyError("empty range")
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
}
@ -41,181 +44,211 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
val counterLabel = asmgen.makeLabel("for_counter")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val stepsize=range.step.constValue(program)?.number
when (stepsize) {
1 -> {
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
val stepsize=range.step.constValue(program)!!.number.toInt()
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
// bytes, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta $loopLabel+1
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sec
sbc $loopLabel+1
adc #0
sta $counterLabel
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
lda ${ESTACK_LO_HEX},x
sta $varname
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sec
sbc $varname
adc #0
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
beq $endLabel
inc $varname
$incdec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
$endLabel inx""")
}
else {
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname""")
if(stepsize>0) {
asmgen.out("""
clc
adc #$stepsize
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcc $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
sec
sbc #${stepsize.absoluteValue}
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcs $loopLabel""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
$endLabel inx""")
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
asmgen.translateExpression(range.to)
asmgen.out(" inc ${MachineDefinition.ESTACK_LO_HEX}+1,x | bne + | inc ${MachineDefinition.ESTACK_HI_HEX}+1,x |+ ")
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
inc $varname
lda $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
bne +
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne +
inc $varname+1
+ lda ${MachineDefinition.ESTACK_HI_HEX}+1,x
cmp $varname+1
""")
} else {
asmgen.out("""
+ lda $varname
bne +
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
cmp $varname
beq $endLabel
dec $varname+1
+ dec $varname""")
}
asmgen.out("""
+ jmp $loopLabel
$endLabel inx""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
}
-1 -> {
when(iterableDt){
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.from)
asmgen.translateExpression(range.to)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
sta $loopLabel+1
sec
sbc ${MachineDefinition.ESTACK_LO_HEX},x
adc #0
sta $counterLabel
inx
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.from)
asmgen.translateExpression(range.to)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
sta $varname
sec
sbc ${MachineDefinition.ESTACK_LO_HEX},x
adc #0
sta $counterLabel
inx
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
stepsize > 0 -> {
// (u)words, step >= 2
asmgen.translateExpression(range.to)
asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
bne +
dec ${MachineDefinition.ESTACK_HI_HEX}+1,x
+ dec ${MachineDefinition.ESTACK_LO_HEX}+1,x
""")
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
bne +
dec $varname+1
+ dec $varname
lda ${MachineDefinition.ESTACK_HI_HEX}+1,x
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_HI_PLUS1_HEX,x
cmp $varname+1
bne +
lda ${MachineDefinition.ESTACK_LO_HEX}+1,x
cmp $varname
beq $endLabel
+ jmp $loopLabel
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcc $endLabel
bcs $loopLabel
$endLabel inx""")
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_LO_PLUS1_HEX,x
cmp $varname
lda $ESTACK_HI_PLUS1_HEX,x
sbc $varname+1
bvc +
eor #$80
+ bpl $loopLabel
$endLabel inx""")
}
}
else -> {
// (u)words, step <= -2
asmgen.translateExpression(range.to)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if(iterableDt==DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcs $loopLabel
$endLabel inx""")
} else {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
cmp $ESTACK_LO_PLUS1_HEX,x
lda $varname+1
sbc $ESTACK_HI_PLUS1_HEX,x
bvc +
eor #$80
+ bpl $loopLabel
$endLabel inx""")
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
}
else -> when (iterableDt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> TODO("non-const forloop bytes, step >1: $stepsize")
DataType.ARRAY_UW, DataType.ARRAY_W -> TODO("non-const forloop words, step >1: $stepsize")
else -> throw AssemblyError("range expression can only be byte or word")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
@ -229,9 +262,7 @@ $endLabel inx""")
val iterableName = asmgen.asmIdentifierName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
when(iterableDt) {
DataType.STR, DataType.STR_S -> {
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
DataType.STR -> {
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
@ -239,8 +270,7 @@ $endLabel inx""")
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $loopLabel+1
@ -250,10 +280,8 @@ $continueLabel inc $loopLabel+1
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
val length = decl.arraysize!!.size()!!
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.out("""
@ -264,8 +292,7 @@ $endLabel""")
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
@ -277,14 +304,12 @@ $counterLabel .byte 0
$endLabel""")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
val length = decl.arraysize!!.size()!! * 2
if(stmt.loopRegister!=null)
throw AssemblyError("can't use register to loop over words")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar)
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
@ -321,7 +346,9 @@ $endLabel""")
}
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases
if (range.isEmpty())
throw AssemblyError("empty range")
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
@ -330,93 +357,12 @@ $endLabel""")
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val counterLabel = asmgen.makeLabel("for_counter")
if(stmt.loopRegister!=null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.last-range.first+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified """)
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
clc
adc #${range.step}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
sec
sbc #${range.step.absoluteValue}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
when {
range.step==1 -> {
// step = 1
asmgen.out("""
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar)
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.last-range.first+1 and 255}
@ -430,34 +376,34 @@ $continueLabel dec $counterLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
@ -467,17 +413,17 @@ $continueLabel dec $counterLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
@ -487,13 +433,12 @@ $continueLabel dec $counterLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val varname = asmgen.asmIdentifierName(stmt.loopVar)
when {
range.step == 1 -> {
// word, step = 1
@ -544,11 +489,59 @@ $endLabel""")
}
range.step >= 2 -> {
// word, step >= 2
TODO("for, word, step>=2")
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel clc
lda $varname
adc #<${range.step}
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
else -> {
// step <= -2
TODO("for, word, step<=-2")
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel sec
lda $varname
sbc #<${range.step.absoluteValue}
sta $varname
lda $varname+1
sbc #>${range.step.absoluteValue}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
}
}

View File

@ -7,112 +7,182 @@ import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers)
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult()
if(saveX)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.arglist.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
translateFuncArguments(arg.first, arg.second, sub)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
argsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers)
if(saveX)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
for (regparam in sub.asmParameterRegisters) {
when (regparam.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
null -> {
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
when (regparam.statusflag) {
Statusflag.Pc -> asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+ pla
""")
null -> {
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
else -> throw AssemblyError("can only use Carry as status flag parameter")
}
}
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, CpuRegister.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName
beq +
sec
@ -120,85 +190,76 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
+ clc
+
""")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+
+ pla
""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
else -> throw AssemblyError("cannot assign to register pair")
}
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
asmgen.assignToRegister(CpuRegister.valueOf(register.name), value.number.toShort(), null)
}
is IdentifierReference -> {
asmgen.assignToRegister(CpuRegister.valueOf(register.name), null, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(valueDt in PassByReferenceDatatypes) {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
@ -206,13 +267,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
else -> {}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
}
@ -222,12 +283,16 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType in StringDatatypes && paramType==DataType.UWORD)
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType in StringDatatypes)
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false

View File

@ -4,39 +4,22 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: PostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val targetRegister = stmt.target.register
when {
targetRegister!=null -> {
when(targetRegister) {
Register.A -> {
if(incr)
asmgen.out(" clc | adc #1 ")
else
asmgen.out(" sec | sbc #1 ")
}
Register.X -> {
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
}
Register.Y -> {
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
}
}
}
targetIdent!=null -> {
val what = asmgen.asmIdentifierName(targetIdent)
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
when (dt) {
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@ -57,15 +40,18 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetMemory!=null -> {
val addressExpr = targetMemory.addressExpression
when (addressExpr) {
when (val addressExpr = targetMemory.addressExpression) {
is NumericLiteralValue -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr)
asmgen.out(if(incr) " inc $what" else " dec $what")
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> throw AssemblyError("weird target type $targetMemory")
}
@ -98,18 +84,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else -> throw AssemblyError("need numeric type")
}
}
is RegisterExpr -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
is IdentifierReference -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
else -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
@ -120,9 +99,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR, DataType.STR_S,
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
}
@ -143,7 +122,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
else -> throw AssemblyError("weird array dt")
}
asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
}
}

View File

@ -7,113 +7,100 @@ import prog8.compiler.CompilerException
import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
class FSignature(val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
val BuiltinFunctions = mapOf(
// this set of function have no return value and operate in-place:
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
"rsave" to FunctionSignature(false, emptyList(), null),
"rrestore" to FunctionSignature(false, emptyList(), null),
"set_carry" to FunctionSignature(false, emptyList(), null),
"clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
// TODO clean up these vm-specific functions
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
"vm_write_char" to FunctionSignature(false, listOf(BuiltinFunctionParam("char", setOf(DataType.UBYTE))), null),
"vm_write_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("string", StringDatatypes)), null),
"vm_input_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("intovar", StringDatatypes)), null),
"vm_gfx_clearscr" to FunctionSignature(false, listOf(BuiltinFunctionParam("color", setOf(DataType.UBYTE))), null),
"vm_gfx_pixel" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x", IntegerDatatypes),
BuiltinFunctionParam("y", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes)), null),
"vm_gfx_line" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x1", IntegerDatatypes),
BuiltinFunctionParam("y1", IntegerDatatypes),
BuiltinFunctionParam("x2", IntegerDatatypes),
BuiltinFunctionParam("y2", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes)), null),
"vm_gfx_text" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x", IntegerDatatypes),
BuiltinFunctionParam("y", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes),
BuiltinFunctionParam("text", StringDatatypes)),
null)
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
"rsave" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"set_carry" to FSignature(false, emptyList(), null),
"clear_carry" to FSignature(false, emptyList(), null),
"set_irqd" to FSignature(false, emptyList(), null),
"clear_irqd" to FSignature(false, emptyList(), null),
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"memcopy" to FSignature(false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
"substr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
"leftstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
)
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
@ -145,10 +132,8 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val idt = arglist.inferType(program)
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
val dt = idt.typeOrElse(DataType.STRUCT)
return when(dt) {
in NumericDatatypes -> dt
in StringDatatypes -> dt
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
@ -171,8 +156,8 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
@ -185,7 +170,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
in StringDatatypes -> InferredTypes.knownFor(DataType.UWORD)
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
else -> InferredTypes.unknown()
}
}
@ -199,7 +184,8 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
class NotConstArgumentException: AstException("not a const argument to a built-in function") // TODO: ugly, remove throwing exceptions for control flow
class NotConstArgumentException: AstException("not a const argument to a built-in function")
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
@ -258,7 +244,7 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
if(argument.type != DataType.STR)
throw SyntaxError("strlen must have string argument", position)
throw NotConstArgumentException() // this function is not considering the string argument a constant
@ -280,22 +266,27 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
arraySize = target.arraysize!!.size()!!
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
arraySize = target.arraysize!!.size()!!
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
DataType.STR -> {
val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")

View File

@ -0,0 +1,157 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.BinaryExpression
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import prog8.ast.statements.PostIncrDecr
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
var optimizationsDone: Int = 0
private val noModifications = emptyList<IAstModification>()
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
// augmented form as wel (with the operator "setvalue")
if (assignment.aug_op == null) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
if (assignment.target.isSameAs(binExpr.left)) {
assignment.value = binExpr.right
assignment.aug_op = binExpr.operator + "="
assignment.value.parent = assignment
optimizationsDone++
return noModifications
}
}
assignment.aug_op = "setvalue"
optimizationsDone++
} else if(assignment.aug_op == "+=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y + 1 -> x += y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y + 2 -> x += y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y - 1 -> x += y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y - 2 -> x += y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
}
}
} else if(assignment.aug_op == "-=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y + 1 -> x -= y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y + 2 -> x -= y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y - 1 -> x -= y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y - 2 -> x -= y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
}
}
}
return noModifications
}
}

View File

@ -6,22 +6,30 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile
private val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
class CallGraph(private val program: Program): IAstVisitor {
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables
class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
init {
@ -31,9 +39,9 @@ class CallGraph(private val program: Program): IAstVisitor {
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) {
scope.statements.forEach {
if(it is Subroutine)
if (it is Subroutine)
sub(it)
if(it is INameScope)
if (it is INameScope)
findSubs(it)
}
}
@ -47,17 +55,8 @@ class CallGraph(private val program: Program): IAstVisitor {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(modulesImportedBy.getValue(it))
it.imports.addAll(modulesImporting.getValue(it))
forAllSubroutines(it) { sub ->
sub.calledBy.clear()
sub.calls.clear()
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
sub.calls.addAll(subroutinesCalling.getValue(sub))
}
it.importedBy.addAll(importedBy.getValue(it))
it.imports.addAll(imports.getValue(it))
}
val rootmodule = program.modules.first()
@ -65,7 +64,7 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(block: Block) {
if(block.definingModule().isLibraryModule) {
if (block.definingModule().isLibraryModule) {
// make sure the block is not removed
addNodeAndParentScopes(block)
}
@ -75,11 +74,11 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(directive: Directive) {
val thisModule = directive.definingModule()
if(directive.directive=="%import") {
val importedModule: Module = program.modules.single { it.name==directive.args[0].name }
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive=="%asminclude") {
if (directive.directive == "%import") {
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
@ -91,7 +90,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(identifier: IdentifierReference) {
// track symbol usage
val target = identifier.targetStatement(this.program.namespace)
if(target!=null) {
if (target != null) {
addNodeAndParentScopes(target)
}
super.visit(identifier)
@ -99,24 +98,18 @@ class CallGraph(private val program: Program): IAstVisitor {
private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt)
var node: Node=stmt
var node: Node = stmt
do {
if(node is INameScope && node is Statement) {
if (node is INameScope && node is Statement) {
usedSymbols.add(node)
}
node=node.parent
node = node.parent
} while (node !is Module && node !is ParentSentinel)
}
override fun visit(subroutine: Subroutine) {
val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
@ -124,12 +117,12 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
addNodeAndParentScopes(decl)
}
if(decl.datatype==DataType.STRUCT)
if (decl.datatype == DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl)
@ -137,10 +130,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
}
}
super.visit(functionCall)
@ -148,10 +141,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
}
}
super.visit(functionCallStatement)
@ -159,10 +152,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
}
}
super.visit(jump)
@ -181,8 +174,6 @@ class CallGraph(private val program: Program): IAstVisitor {
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@ -190,27 +181,27 @@ class CallGraph(private val program: Program): IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
} else if(jumptarget.contains('.')) {
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target= matches2.groups[2]?.value
val target = matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
if(target.contains('.')) {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
}

View File

@ -54,15 +54,15 @@ class ConstExprEvaluator {
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-bitxor $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -71,15 +71,15 @@ class ConstExprEvaluator {
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -88,15 +88,15 @@ class ConstExprEvaluator {
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -144,15 +144,15 @@ class ConstExprEvaluator {
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot calculate $left ** $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -161,15 +161,15 @@ class ConstExprEvaluator {
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -178,15 +178,15 @@ class ConstExprEvaluator {
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -195,15 +195,15 @@ class ConstExprEvaluator {
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -215,25 +215,25 @@ class ConstExprEvaluator {
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot divide $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
}
@ -245,24 +245,24 @@ class ConstExprEvaluator {
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute remainder of $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
}

View File

@ -1,693 +0,0 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) {
// check that we don't add the isSameAs error more than once
if(x.toString() !in reportedErrorMessages) {
reportedErrorMessages.add(x.toString())
errors.add(x)
}
}
override fun visit(decl: VarDecl): Statement {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call tree for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
return decl
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
optimizationsDone++
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this)
if(size is NumericLiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
decl.value = newValue
optimizationsDone++
return super.visit(decl)
}
}
in StringDatatypes -> {
// nothing to do for strings
}
DataType.STRUCT -> {
// struct defintions don't have anything else in them
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return super.visit(decl)
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
val size = decl.arraysize?.size() ?: return decl
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", numericLv.position))
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", numericLv.position))
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", numericLv.position))
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
refValue.addToHeap(program.heap)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl
val litval = decl.value as? NumericLiteralValue
if(litval==null) {
// there's no initialization value, but the size is known, so we're ok.
return super.visit(decl)
} else {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval.position))
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
refValue.addToHeap(program.heap)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
}
else -> {
// nothing to do for this type
}
}
}
return super.visit(decl)
}
/**
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/
override fun visit(identifier: IdentifierReference): Expression {
// don't replace when it's an assignment target or loop variable
if(identifier.parent is AssignTarget)
return identifier
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return identifier
return try {
val cval = identifier.constValue(program) ?: return identifier
return when {
cval.type in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
else -> identifier
}
} catch (ax: AstException) {
addError(ax)
identifier
}
}
override fun visit(functionCall: FunctionCall): Expression {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return try {
functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) {
addError(ax)
functionCall
}
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
super.visit(functionCallStatement)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
if(functionCall.target.nameInSource.size==1) {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) {
// match the arguments of a builtin function signature.
for(arg in functionCall.arglist.withIndex().zip(builtinFunction.parameters)) {
val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first())
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
}
}
// match the arguments of a subroutine.
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.visit(addrOf.identifier)
return super.visit(memread)
}
/**
* Try to accept a unary prefix expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
*/
override fun visit(expr: PrefixExpression): Expression {
return try {
val prefixExpr=super.visit(expr)
if(prefixExpr !is PrefixExpression)
return prefixExpr
val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when {
prefixExpr.operator == "+" -> subexpr
prefixExpr.operator == "-" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
}
subexpr.type == DataType.FLOAT -> {
optimizationsDone++
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
prefixExpr.operator == "~" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
prefixExpr.operator == "not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
}
}
return prefixExpr
} catch (ax: AstException) {
addError(ax)
expr
}
}
/**
* Try to accept a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun visit(expr: BinaryExpression): Expression {
return try {
super.visit(expr)
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
return groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
}
}
// const fold when both operands are a const
return when {
leftconst != null && rightconst != null -> {
optimizationsDone++
val evaluator = ConstExprEvaluator()
evaluator.evaluate(leftconst, expr.operator, rightconst)
}
else -> expr
}
} catch (ax: AstException) {
addError(ax)
expr
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): Expression
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr.
if(expr.operator=="+" || expr.operator=="*") {
if(leftIsConst) {
if(subleftIsConst)
expr.left = subExpr.right.also { subExpr.right = expr.left }
else
expr.left = subExpr.left.also { subExpr.left = expr.left }
} else {
if(subleftIsConst)
expr.right = subExpr.right.also {subExpr.right = expr.right }
else
expr.right = subExpr.left.also { subExpr.left = expr.right }
}
optimizationsDone++
return expr
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
val tmp = subExpr.right
subExpr.right = subExpr.left
subExpr.left = expr.left
expr.left = tmp
expr.operator = if(expr.operator=="-") "+" else "*"
expr
} else
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position)
} else {
return if(subleftIsConst) {
expr.right = subExpr.right.also { subExpr.right = expr.right }
expr
} else
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position)
}
}
return expr
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
optimizationsDone++
if(leftIsConst){
return if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
return expr
}
}
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue
try {
newFrom = rangeFrom.cast(targetDt)
newTo = rangeTo.cast(targetDt)
} catch (x: ExpressionError) {
return range
}
val newStep: Expression = stepLiteral?.cast(targetDt) ?: range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
val resultStmt = super.visit(forLoop) as ForLoop
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) {
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
}
return resultStmt
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
if(array.heapId==null)
array.addToHeap(program.heap)
val vardecl = array.parent as? VarDecl
return if (vardecl!=null) {
fixupArrayDatatype(array, vardecl, program)
} else {
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
fixupArrayDatatype(array, program)
}
}
return array
}
override fun visit(assignment: Assignment): Statement {
super.visit(assignment)
val lv = assignment.value as? NumericLiteralValue
if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype
val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown)
return assignment
when(idt.typeOrElse(DataType.STRUCT)) {
DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=65535)
assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
}
}
DataType.UBYTE -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d >=0 && d<=255)
assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
}
}
DataType.BYTE -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=127)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.WORD -> {
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=-32768 && d<=32767)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.FLOAT -> {
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
}
else -> {}
}
}
return assignment
}
}

View File

@ -0,0 +1,592 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
// First thing to do is replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if(identifier.parent is AssignTarget)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
decl
))
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(numericLv!=null && numericLv.type==DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.size() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return noModifications
val litval = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
}
return noModifications
}
}
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
// Try to turn a unary prefix expression into a single constant value.
// Compile-time constant sub expressions will be evaluated on the spot.
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
val subexpr = expr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when (expr.operator) {
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
parent))
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
}
}
return noModifications
}
/**
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
val change = groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
return change?.let { listOf(it) } ?: noModifications
}
}
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
return listOf(IAstModification.ReplaceNode(
expr,
evaluator.evaluate(leftconst, expr.operator, rightconst),
parent
))
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
// because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype.
if(array.type.isKnown)
return noModifications
// if the array literalvalue is inside an array vardecl, take the type from that
// otherwise infer it from the elements of the array
val vardeclType = (array.parent as? VarDecl)?.datatype
if(vardeclType!=null) {
val newArray = array.cast(vardeclType)
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
}
}
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already.
val constvalue = functionCall.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
else
noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue
try {
newFrom = rangeFrom.cast(targetDt)
newTo = rangeTo.cast(targetDt)
} catch (x: ExpressionError) {
return range
}
val newStep: Expression = try {
stepLiteral?.cast(targetDt)?: range.step
} catch(ee: ExpressionError) {
range.step
}
return RangeExpr(newFrom, newTo, newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
val newExprLeft: Expression?,
val newExprRight: Expression?,
val newSubexprLeft: Expression?,
val newSubexprRight: Expression?
): IAstModification {
override fun perform() {
if(exprOperator!=null) expr.operator = exprOperator
if(newExprLeft!=null) expr.left = newExprLeft
if(newExprRight!=null) expr.right = newExprRight
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
if(newSubexprRight!=null) subExpr.right = newSubexprRight
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the same.
// If + or *, we can simply shuffle the const operands around to optimize.
if(expr.operator=="+" || expr.operator=="*") {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
else
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
} else {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
else
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
}
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
if(leftIsConst) {
return if (subleftIsConst) {
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position),
expr.parent)
}
} else {
return if(subleftIsConst) {
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position),
expr.parent)
}
}
}
return null
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
if(leftIsConst){
val change = if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
return null
}
}
}

View File

@ -0,0 +1,668 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
/*
todo add more expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>()
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if (assignment.aug_op != null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteralValue
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral !== literal)
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
}
// remove redundant nested typecasts:
// if the typecast casts a value to the same type, remove the cast.
// if the typecast contains another typecast, remove the inner typecast.
val subTypecast = typecast.expression as? TypecastExpression
if (subTypecast != null) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
} else {
if (typecast.expression.inferType(program).istype(typecast.type))
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
return mods
}
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if (expr.operator == "+") {
// +X --> X
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
} else if (expr.operator == "not") {
when(expr.expression) {
is PrefixExpression -> {
// NOT(NOT(...)) -> ...
val pe = expr.expression as PrefixExpression
if(pe.operator == "not")
return listOf(IAstModification.ReplaceNode(expr, pe.expression, parent))
}
is BinaryExpression -> {
// NOT (xxxx) -> invert the xxxx
val be = expr.expression as BinaryExpression
val newExpr = when (be.operator) {
"<" -> BinaryExpression(be.left, ">=", be.right, be.position)
">" -> BinaryExpression(be.left, "<=", be.right, be.position)
"<=" -> BinaryExpression(be.left, ">", be.right, be.position)
">=" -> BinaryExpression(be.left, "<", be.right, be.position)
"==" -> BinaryExpression(be.left, "!=", be.right, be.position)
"!=" -> BinaryExpression(be.left, "==", be.right, be.position)
else -> null
}
if (newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
else -> return noModifications
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if (!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
return listOf(IAstModification.SwapOperands(expr))
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "-", (expr.right as PrefixExpression).expression, expr.position),
parent
))
}
// (-A) + X --> X - A
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.right, "-", (expr.left as PrefixExpression).expression, expr.position),
parent
))
}
// X - (-A) --> X + A
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "+", (expr.right as PrefixExpression).expression, expr.position),
parent
))
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if (leftBinExpr?.operator == "*") {
if (expr.operator == "+") {
// Y*X + X -> X*(Y + 1)
// X*Y + X -> X*(Y + 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
} else {
// Y*X - X -> X*(Y - 1)
// X*Y - X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
} else if (rightBinExpr?.operator == "*") {
if (expr.operator == "+") {
// X + Y*X -> X*(Y + 1)
// X + X*Y -> X*(Y + 1)
val x = expr.left
val y = determineY(x, rightBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
}
}
if(expr.operator == ">=" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed >=0 --> signed ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw >=0 --> msb(signedw) ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed < 0 --> signed & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw < 0 --> msb(signedw) & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
// simplify when a term is constant and directly determines the outcome
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
constTrue
else if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
constFalse
else if (leftVal != null && leftVal.asBooleanValue)
expr.right
else if (rightVal != null && rightVal.asBooleanValue)
expr.left
else
null
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else if (leftVal != null && leftVal.asBooleanValue)
PrefixExpression("not", expr.right, expr.right.position)
else if (rightVal != null && rightVal.asBooleanValue)
PrefixExpression("not", expr.left, expr.left.position)
else
null
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue)
constFalse
else if (rightVal != null && !rightVal.asBooleanValue)
constFalse
else
null
}
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
"/" -> optimizeDivision(expr, leftVal, rightVal)
"+" -> optimizeAdd(expr, leftVal, rightVal)
"-" -> optimizeSub(expr, leftVal, rightVal)
"**" -> optimizePower(expr, leftVal, rightVal)
"%" -> optimizeRemainder(expr, leftVal, rightVal)
">>" -> optimizeShiftRight(expr, rightVal)
"<<" -> optimizeShiftLeft(expr, rightVal)
else -> null
}
if(newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left
else -> null
}
}
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X+X into X *2
expr.operator = "*"
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
expr.right.linkParents(expr)
return expr
}
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr2.left
}
}
}
// no need to check for left val constant (because of associativity)
return null
}
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X-X into 0
return NumericLiteralValue.optimalInteger(0, expr.position)
}
if (leftVal == null && rightVal == null)
return null
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr.left
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
0.0 -> {
// -right
return PrefixExpression("-", expr.right, expr.position)
}
}
}
return null
}
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
-3.0 -> {
// -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position)
}
-2.0 -> {
// -1/(left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position)
}
-1.0 -> {
// -1/left
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position)
}
0.0 -> {
// 1
return NumericLiteralValue(rightConst.type, 1, expr.position)
}
0.5 -> {
// sqrt(left)
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
}
1.0 -> {
// left
return expr.left
}
2.0 -> {
// left*left
return BinaryExpression(expr.left, "*", expr.left, expr.position)
}
3.0 -> {
// left*left*left
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
-1.0 -> {
// -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
1.0 -> {
//1
return NumericLiteralValue(leftVal.type, 1, expr.position)
}
}
}
return null
}
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
// simplify assignments A = B <operator> C
val cv = rightVal?.number?.toInt()?.toDouble()
when (expr.operator) {
"%" -> {
if (cv == 1.0) {
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return null
}
}
}
return null
}
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
// cannot shuffle assiciativity with division!
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when (cv) {
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
return PrefixExpression("-", expr.left, expr.position)
}
}
1.0 -> {
// '/' -> left
if (expr.operator == "/") {
return expr.left
}
}
in powersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a power of two => shift right
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number.toDouble()) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
} else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number.toDouble()) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
}
}
return null
}
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2
when (val cv = rightConst.number.toDouble()) {
-1.0 -> {
// -left
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(rightConst.type, 0, expr.position)
}
1.0 -> {
// left
return expr2.left
}
in powersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left
val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
}
// no need to check for left val constant (because of associativity)
return null
}
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
if (amountLv == null)
return null
val amount = amountLv.number.toInt()
if (amount == 0) {
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when (targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) {
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
}
}
else -> {
}
}
return null
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
if (amountLv == null)
return null
val amount = amountLv.number.toInt()
if (amount == 0) {
return expr.left
}
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if (amount > 8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return null
}
}
DataType.UWORD -> {
if (amount >= 16) {
return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8)
return msb
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
DataType.WORD -> {
if (amount > 16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return null
} else if (amount >= 8) {
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if (amount == 8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
else -> {
}
}
return null
}
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
if (expr.operator in associativeOperators && leftVal != null) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
}
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
}
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
}

View File

@ -1,42 +1,44 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.parser.ParsingFailedError
import prog8.ast.base.ErrorReporter
internal fun Program.constantFold() {
val optimizer = ConstantFolding(this)
try {
internal fun Program.constantFold(errors: ErrorReporter) {
val replacer = ConstantIdentifierReplacer(this, errors)
replacer.visit(this)
if(errors.isEmpty()) {
replacer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.visit(this)
} catch (ax: AstException) {
optimizer.addError(ax)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if(errors.isEmpty()) {
replacer.visit(this)
replacer.applyModifications()
}
}
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
optimizer.optimizationsDone = 0
optimizer.visit(this)
}
if(optimizer.errors.isNotEmpty()) {
optimizer.errors.forEach { System.err.println(it) }
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
} else {
if(errors.isEmpty())
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
}
}
internal fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(this)
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
val optimizer = StatementOptimizer(this, errors)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone
return optimizationCount
}
internal fun Program.simplifyExpressions() : Int {
val optimizer = SimplifyExpressions(this)
optimizer.visit(this)
return optimizer.optimizationsDone
val opti = ExpressionSimplifier(this)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -1,830 +0,0 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
/*
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
Also see https://egorbo.com/peephole-optimizations.html
*/
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
override fun visit(assignment: Assignment): Statement {
if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
return super.visit(assignment)
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.visit(addrOf.identifier)
return super.visit(memread)
}
override fun visit(typecast: TypecastExpression): Expression {
var tc = typecast
// try to statically convert a literal value into one of the desired type
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!==literal) {
optimizationsDone++
return newLiteral
}
}
// remove redundant typecasts
while(true) {
val expr = tc.expression
if(expr !is TypecastExpression || expr.type!=tc.type) {
val assignment = typecast.parent as? Assignment
if(assignment!=null) {
val targetDt = assignment.target.inferType(program, assignment)
if(tc.expression.inferType(program)==targetDt) {
optimizationsDone++
return tc.expression
}
}
val subTc = tc.expression as? TypecastExpression
if(subTc!=null) {
// if the previous typecast was casting to a 'bigger' type, just ignore that one
// if the previous typecast was casting to a similar type, ignore that one
if(subTc.type largerThan tc.type || subTc.type equalsSize tc.type) {
subTc.type = tc.type
subTc.parent = tc.parent
optimizationsDone++
return subTc
}
}
return super.visit(tc)
}
optimizationsDone++
tc = expr
}
}
override fun visit(expr: PrefixExpression): Expression {
if (expr.operator == "+") {
// +X --> X
optimizationsDone++
return expr.expression.accept(this)
} else if (expr.operator == "not") {
(expr.expression as? BinaryExpression)?.let {
// NOT (...) -> invert ...
when (it.operator) {
"<" -> {
it.operator = ">="
optimizationsDone++
return it
}
">" -> {
it.operator = "<="
optimizationsDone++
return it
}
"<=" -> {
it.operator = ">"
optimizationsDone++
return it
}
">=" -> {
it.operator = "<"
optimizationsDone++
return it
}
"==" -> {
it.operator = "!="
optimizationsDone++
return it
}
"!=" -> {
it.operator = "=="
optimizationsDone++
return it
}
else -> {
}
}
}
}
return super.visit(expr)
}
override fun visit(expr: BinaryExpression): Expression {
super.visit(expr)
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
if (leftDt != rightDt) {
// try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++
return expr
}
}
// Value <associativeoperator> X --> X <associativeoperator> Value
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) {
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
optimizationsDone++
return expr
}
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
expr.operator = "-"
expr.right = (expr.right as PrefixExpression).expression
optimizationsDone++
return expr
}
// (-A) + X --> X - A
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
expr.operator = "-"
val newRight = (expr.left as PrefixExpression).expression
expr.left = expr.right
expr.right = newRight
optimizationsDone++
return expr
}
// X + (-value) --> X - value
if (expr.operator == "+" && rightVal != null) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
}
// (-value) + X --> X - value
if (expr.operator == "+" && leftVal != null) {
val lv = leftVal.number.toDouble()
if (lv < 0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
optimizationsDone++
return expr
}
}
// X - (-A) --> X + A
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
expr.operator = "+"
expr.right = (expr.right as PrefixExpression).expression
optimizationsDone++
return expr
}
// X - (-value) --> X + value
if (expr.operator == "-" && rightVal != null) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
}
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if (leftBinExpr?.operator == "*") {
if (expr.operator == "+") {
// Y*X + X -> X*(Y - 1)
// X*Y + X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position)
}
} else {
// Y*X - X -> X*(Y - 1)
// X*Y - X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if(y!=null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yMinus1, x.position)
}
}
}
else if(rightBinExpr?.operator=="*") {
if(expr.operator=="+") {
// X + Y*X -> X*(Y + 1)
// X + X*Y -> X*(Y + 1)
val x = expr.left
val y = determineY(x, rightBinExpr)
if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position)
}
} else {
// X - Y*X -> X*(1 - Y)
// X - X*Y -> X*(1 - Y)
val x = expr.left
val y = determineY(x, rightBinExpr)
if(y!=null) {
val oneMinusY = BinaryExpression(NumericLiteralValue.optimalInteger(1, y.position), "-", y, y.position)
return BinaryExpression(x, "*", oneMinusY, x.position)
}
}
}
}
// simplify when a term is constant and determines the outcome
when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue)) {
optimizationsDone++
return constTrue
}
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue)) {
optimizationsDone++
return constFalse
}
if (leftVal != null && leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
if (leftVal != null && leftVal.asBooleanValue) {
optimizationsDone++
return PrefixExpression("not", expr.right, expr.right.position)
}
if (rightVal != null && rightVal.asBooleanValue) {
optimizationsDone++
return PrefixExpression("not", expr.left, expr.left.position)
}
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return constFalse
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return constFalse
}
}
"*" -> return optimizeMultiplication(expr, leftVal, rightVal)
"/" -> return optimizeDivision(expr, leftVal, rightVal)
"+" -> return optimizeAdd(expr, leftVal, rightVal)
"-" -> return optimizeSub(expr, leftVal, rightVal)
"**" -> return optimizePower(expr, leftVal, rightVal)
"%" -> return optimizeRemainder(expr, leftVal, rightVal)
">>" -> return optimizeShiftRight(expr, rightVal)
"<<" -> return optimizeShiftLeft(expr, rightVal)
}
return expr
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left
else -> null
}
}
private fun adjustDatatypes(expr: BinaryExpression,
leftConstVal: NumericLiteralValue?, leftDt: DataType,
rightConstVal: NumericLiteralValue?, rightDt: DataType): Boolean {
fun adjust(value: NumericLiteralValue, targetDt: DataType): Pair<Boolean, NumericLiteralValue>{
if(value.type==targetDt)
return Pair(false, value)
when(value.type) {
DataType.UBYTE -> {
if (targetDt == DataType.BYTE) {
if(value.number.toInt() < 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
DataType.BYTE -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
else if (targetDt == DataType.WORD) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
DataType.UWORD -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() <= 255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.number.toInt() <= 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.WORD) {
if(value.number.toInt() <= 32767)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
}
DataType.WORD -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() in 0..255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.number.toInt() in -128..127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
}
else -> {}
}
return Pair(false, value)
}
if(leftConstVal==null && rightConstVal!=null) {
if(leftDt largerThan rightDt) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if (adjusted) {
expr.right = newValue
optimizationsDone++
return true
}
}
return false
} else if(leftConstVal!=null && rightConstVal==null) {
if(rightDt largerThan leftDt) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if (adjusted) {
expr.left = newValue
optimizationsDone++
return true
}
}
return false
} else {
return false // two const values, don't adjust (should have been const-folded away)
}
}
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
if(expr.operator in associativeOperators && leftVal!=null) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
optimizationsDone++
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
}
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
}
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
return expr.left
}
}
}
// no need to check for left val constant (because of associativity)
return expr
}
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
return expr.left
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
0.0 -> {
// -right
optimizationsDone++
return PrefixExpression("-", expr.right, expr.position)
}
}
}
return expr
}
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
-3.0 -> {
// -1/(left*left*left)
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position)
}
-2.0 -> {
// -1/(left*left)
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position)
}
-1.0 -> {
// -1/left
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position)
}
0.0 -> {
// 1
optimizationsDone++
return NumericLiteralValue(rightConst.type, 1, expr.position)
}
0.5 -> {
// sqrt(left)
optimizationsDone++
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
}
1.0 -> {
// left
optimizationsDone++
return expr.left
}
2.0 -> {
// left*left
optimizationsDone++
return BinaryExpression(expr.left, "*", expr.left, expr.position)
}
3.0 -> {
// left*left*left
optimizationsDone++
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
-1.0 -> {
// -1
optimizationsDone++
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
1.0 -> {
//1
optimizationsDone++
return NumericLiteralValue(leftVal.type, 1, expr.position)
}
}
}
return expr
}
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
// simplify assignments A = B <operator> C
val cv = rightVal?.number?.toInt()?.toDouble()
when(expr.operator) {
"%" -> {
if (cv == 1.0) {
optimizationsDone++
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
optimizationsDone++
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return expr
}
}
}
return expr
}
private val powersOfTwo = (1 .. 16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
// TODO fix bug in this routine!
// cannot shuffle assiciativity with division!
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val leftIDt = expr.left.inferType(program)
if(!leftIDt.isKnown)
return expr
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when(cv) {
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
optimizationsDone++
return PrefixExpression("-", expr.left, expr.position)
}
}
1.0 -> {
// '/' -> left
if (expr.operator == "/") {
optimizationsDone++
return expr.left
}
}
in powersOfTwo -> {
if(leftDt in IntegerDatatypes) {
// divided by a power of two => shift right
optimizationsDone++
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if(leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right
optimizationsDone++
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
if (leftDt == DataType.UBYTE) {
if(abs(rightConst.number.toDouble()) >= 256.0) {
optimizationsDone++
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
else if (leftDt == DataType.UWORD) {
if(abs(rightConst.number.toDouble()) >= 65536.0) {
optimizationsDone++
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
}
}
return expr
}
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr.left
val rightConst: NumericLiteralValue = rightVal
when(val cv = rightConst.number.toDouble()) {
-1.0 -> {
// -left
optimizationsDone++
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(rightConst.type, 0, expr.position)
}
1.0 -> {
// left
optimizationsDone++
return expr.left
}
in powersOfTwo -> {
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left
optimizationsDone++
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left
optimizationsDone++
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
}
// no need to check for left val constant (because of associativity)
return expr
}
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if(amount==8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
}
}
else -> {}
}
return expr
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if(amount>8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return expr
}
}
DataType.UWORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if(amount==8)
return msb
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
DataType.WORD -> {
if(amount>16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return expr
} else if(amount>=8) {
optimizationsDone++
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if(amount==8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
else -> {}
}
return expr
}
}

View File

@ -1,103 +1,56 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
/*
TODO: analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
TODO: proper inlining of small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
TODO: remove unreachable code after return and exit()
*/
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
private set
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() {
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program)
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun visit(program: Program) {
removeUnusedCode(callgraph)
super.visit(program)
}
private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>()
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removeSubroutines.add(sub)
}
}
if (removeSubroutines.isNotEmpty()) {
removeSubroutines.forEach {
it.definingScope().remove(it)
}
}
val removeBlocks = mutableSetOf<Block>()
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removeBlocks.add(block)
}
if (removeBlocks.isNotEmpty()) {
removeBlocks.forEach { it.definingScope().remove(it) }
}
// remove modules that are not imported, or are empty (unless it's a library modules)
val removeModules = mutableSetOf<Module>()
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removeModules.add(it)
}
if (removeModules.isNotEmpty()) {
program.modules.removeAll(removeModules)
}
}
override fun visit(block: Block): Statement {
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position)
return NopStatement.insteadOf(block)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
return NopStatement.insteadOf(block) // remove unused block
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
}
}
return super.visit(block)
return noModifications
}
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.parent)
}.toMutableList()
removals += IAstModification.Remove(subroutine, parent)
return removals
}
}
@ -107,31 +60,367 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
}
return subroutine
return noModifications
}
override fun visit(decl: VarDecl): Statement {
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val linesToRemove = deduplicateAssignments(scope.statements)
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR)
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement.insteadOf(decl)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent))
}
return super.visit(decl)
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
val pos = functionCallStatement.position
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
}
}
}
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is ReturnFromIrq || first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
}
}
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
)
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
}
}
return noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
if(iterable!=null) {
if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
else if(iterable.datatype in ArrayDatatypes) {
val size = iterable.arraysize!!.size()
if(size==1) {
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
}
}
return noModifications
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val constvalue = untilLoop.untilCondition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no continue and break statements)
errors.warn("condition is always true", untilLoop.untilCondition.position)
if(!hasContinueOrBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else {
// always false
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
}
}
return noModifications
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue) {
// always true
val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
}
}
return noModifications
}
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
}
if (iterations == 1)
errors.warn("iterations is always 1", iter.position)
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
// remove assignments to self
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace))
return listOf(IAstModification.Remove(assignment, parent))
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
}
}
}
"-" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"<<" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
">>" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
}
}
}
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
// removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) {
for (i in statements.indices) {
val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) {
@ -152,227 +441,21 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
return linesToRemove
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) {
val heapId = stringVar.heapId(program.namespace)
val string = program.heap.get(heapId).str!!
if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0]
functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(petscii.toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
optimizationsDone++
return functionCallStatement
} else if(string.length==2) {
val petscii = Petscii.encodePetscii(string, true)
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(petscii[0].toInt(), functionCallStatement.position)), functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(petscii[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
optimizationsDone++
return scope
}
}
}
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
}
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return constval
}
}
return super.visit(functionCall)
}
override fun visit(ifStatement: IfStatement): Statement {
super.visit(ifStatement)
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
optimizationsDone++
return NopStatement.insteadOf(ifStatement)
}
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
// invert the condition and move else part to true part
ifStatement.truepart = ifStatement.elsepart
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
optimizationsDone++
return ifStatement
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
printWarning("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
printWarning("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
}
return ifStatement
}
override fun visit(forLoop: ForLoop): Statement {
super.visit(forLoop)
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment)
optimizationsDone++
return forLoop.body
}
}
return forLoop
}
override fun visit(whileLoop: WhileLoop): Statement {
super.visit(whileLoop)
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always true", whileLoop.position)
if(hasContinueOrBreak(whileLoop.body))
return whileLoop
val label = Label("_prog8_back", whileLoop.condition.position)
whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position),
null, whileLoop.condition.position))
optimizationsDone++
return whileLoop.body
} else {
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position)
optimizationsDone++
NopStatement.insteadOf(whileLoop)
}
}
return whileLoop
}
override fun visit(repeatLoop: RepeatLoop): Statement {
super.visit(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements)
printWarning("condition is always true", repeatLoop.position)
if(hasContinueOrBreak(repeatLoop.body))
repeatLoop
else {
optimizationsDone++
repeatLoop.body
}
} else {
// always false -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always false", repeatLoop.position)
if(hasContinueOrBreak(repeatLoop.body))
return repeatLoop
val label = Label("__back", repeatLoop.untilCondition.position)
repeatLoop.body.statements.add(0, label)
repeatLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), repeatLoop.untilCondition.position),
null, repeatLoop.untilCondition.position))
optimizationsDone++
return repeatLoop.body
}
}
return repeatLoop
}
override fun visit(whenStatement: WhenStatement): Statement {
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.statements.containsNoCodeNorVars())
whenStatement.choices.remove(choice)
}
return super.visit(whenStatement)
}
private fun hasContinueOrBreak(scope: INameScope): Boolean {
class Searcher: IAstModifyingVisitor
class Searcher: IAstVisitor
{
var count=0
override fun visit(breakStmt: Break): Statement {
override fun visit(breakStmt: Break) {
count++
return super.visit(breakStmt)
}
override fun visit(contStmt: Continue): Statement {
override fun visit(contStmt: Continue) {
count++
return super.visit(contStmt)
}
}
val s=Searcher()
for(stmt in scope.statements) {
stmt.accept(s)
@ -382,219 +465,4 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
return s.count > 0
}
override fun visit(jump: Jump): Statement {
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump) {
optimizationsDone++
return first
}
}
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++
return NopStatement.insteadOf(jump)
}
}
return jump
}
override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw AssemblyError("can't infer type of assignment target")
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
}
} else {
if (assignment.target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return decs
}
}
}
"-" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return decs
}
}
}
"*" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"/" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"**" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"|" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"^" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"<<" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
}
">>" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
}
}
}
}
}
return super.visit(assignment)
}
override fun visit(scope: AnonymousScope): Statement {
val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
}
return super.visit(scope)
}
override fun visit(label: Label): Statement {
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
return NopStatement.insteadOf(label)
return super.visit(label)
}
}
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}

View File

@ -0,0 +1,38 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
internal class UnusedCodeRemover: AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
}
return removals
}
}

View File

@ -4,11 +4,13 @@ import org.antlr.v4.runtime.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.pathFrom
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
@ -32,114 +34,117 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal class ModuleImporter(private val errors: ErrorReporter) {
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(Paths.get(source.parent.toString()))
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(Paths.get(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(Paths.get(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = Paths.get(it.toString(), fileName)
if (Files.isReadable(file)) return file
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
val resource = tryGetEmbeddedResource(moduleName+".p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(pathFrom(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(pathFrom(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = pathFrom(it.toString(), fileName)
if (Files.isReadable(file)) return file
}
importedModule.checkImportedValid()
return importedModule
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val resource = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid()
return importedModule
}
private fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}
}

View File

@ -0,0 +1,22 @@
package prog8.server.dbus
//import org.freedesktop.dbus.interfaces.DBusInterface
//
//
//interface IrmenDbusTest: DBusInterface
//{
// fun Status(address: String): Map<Int, String>
//}
//
//
//internal class TestService: IrmenDbusTest {
// override fun Status(address: String): Map<Int, String> {
// return mapOf(
// 5 to "hello",
// 42 to address
// )
// }
//
// override fun isRemote() = true
// override fun getObjectPath() = "/razorvine/TestService"
//}

View File

@ -0,0 +1,17 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
// println(obj.Status("irmen"))
// }
//}
//

View File

@ -0,0 +1,18 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// it.requestBusName("local.net.razorvine.dbus.test")
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val service = TestService()
// it.exportObject(service.objectPath, service)
//
// Thread.sleep(100000)
// }
//}

View File

@ -1,625 +0,0 @@
package prog8.vm
import prog8.ast.base.*
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import java.util.*
import kotlin.math.abs
import kotlin.math.pow
/**
* Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null,
val array: Array<Number>?=null, val heapId: Int?=null) {
val byteval: Short?
val wordval: Int?
val floatval: Double?
val asBoolean: Boolean
companion object {
fun fromLv(literalValue: NumericLiteralValue): RuntimeValue {
return RuntimeValue(literalValue.type, num = literalValue.number)
}
fun fromLv(string: StringLiteralValue, heap: HeapValues): RuntimeValue = fromHeapId(string.heapId!!, heap)
fun fromLv(array: ArrayLiteralValue, heap: HeapValues): RuntimeValue = fromHeapId(array.heapId!!, heap)
fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId)
return when {
value.type in StringDatatypes ->
RuntimeValue(value.type, str = value.str!!, heapId = heapId)
value.type in ArrayDatatypes ->
if (value.type == DataType.ARRAY_F) {
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
} else {
val array = value.array!!
val resultArray = mutableListOf<Number>()
for(elt in array.withIndex()){
if(elt.value.integer!=null)
resultArray.add(elt.value.integer!!)
else {
TODO("ADDRESSOF ${elt.value}")
}
}
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
//RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> throw IllegalArgumentException("weird value type on heap $value")
}
}
}
init {
when(type) {
DataType.UBYTE -> {
val inum = num!!.toInt()
if(inum !in 0 .. 255)
throw IllegalArgumentException("invalid value for ubyte: $inum")
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.BYTE -> {
val inum = num!!.toInt()
if(inum !in -128 .. 127)
throw IllegalArgumentException("invalid value for byte: $inum")
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.UWORD -> {
val inum = num!!.toInt()
if(inum !in 0 .. 65535)
throw IllegalArgumentException("invalid value for uword: $inum")
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.WORD -> {
val inum = num!!.toInt()
if(inum !in -32768 .. 32767)
throw IllegalArgumentException("invalid value for word: $inum")
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.FLOAT -> {
floatval = num!!.toDouble()
byteval = null
wordval = null
asBoolean = floatval != 0.0
}
else -> {
byteval = null
wordval = null
floatval = null
asBoolean = true
}
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
"b:-%02x".format(abs(byteval.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
"w:-%04x".format(abs(wordval))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ArithmeticException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
else -> throw ArithmeticException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
return false
if(type==other.type)
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValue): Int {
return if (type in NumericDatatypes && other.type in NumericDatatypes)
numericValue().toDouble().compareTo(other.numericValue().toDouble())
else throw ArithmeticException("comparison can only be done between two numeric values")
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValue {
if(leftDt!=rightDt)
throw ArithmeticException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt== DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v=result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.WORD -> {
val v=result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> {
val v = result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> {
val v = result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
fun add(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
when (type) {
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767)
else -> {}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result)
DataType.UWORD -> RuntimeValue(DataType.UWORD, result)
DataType.WORD -> RuntimeValue(DataType.WORD, result)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type")
}
}
fun remainder(other: RuntimeValue): RuntimeValue {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: RuntimeValue): RuntimeValue {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): RuntimeValue {
val v = integerValue()
return when (type) {
DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535)
DataType.BYTE -> {
val value = v shl 1
if(value<128)
RuntimeValue(type, value)
else
RuntimeValue(type, value-256)
}
DataType.WORD -> {
val value = v shl 1
if(value<32768)
RuntimeValue(type, value)
else
RuntimeValue(type, value-65536)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValue {
val v = integerValue()
return when(type){
DataType.UBYTE -> RuntimeValue(type, v ushr 1)
DataType.BYTE -> RuntimeValue(type, v shr 1)
DataType.UWORD -> RuntimeValue(type, v ushr 1)
DataType.WORD -> RuntimeValue(type, v shr 1)
else -> throw ArithmeticException("invalid type for shr: $type")
}
}
fun rol(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValue {
// 8 or 16 bit rotate left
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValue {
// 8 or 16 bit rotate right
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun neg(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float")
}
}
fun abs(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!))
else -> throw ArithmeticException("abs can only work on byte/word/float")
}
}
fun bitand(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return RuntimeValue(type, result)
}
fun bitor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return RuntimeValue(type, result)
}
fun bitxor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return RuntimeValue(type, result)
}
fun and(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
fun or(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
fun xor(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun inv(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValue(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! + 1
if(newval == 128)
RuntimeValue(type, -128)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if(newval == 32768)
RuntimeValue(type, -32768)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types")
}
}
fun dec(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! - 1
if(newval == -129)
RuntimeValue(type, 127)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if(newval == -32769)
RuntimeValue(type, 32767)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types")
}
}
fun msb(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
}
}
fun cast(targetType: DataType): RuntimeValue {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
val nval=byteval!!.toInt()
if(nval<128)
RuntimeValue(DataType.BYTE, nval)
else
RuntimeValue(DataType.BYTE, nval-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
DataType.WORD -> {
val nval = numericValue().toInt()
if(nval<32768)
RuntimeValue(DataType.WORD, nval)
else
RuntimeValue(DataType.WORD, nval-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE -> {
val v=integerValue()
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
val v=integerValue()
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE -> {
val v = integerValue() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer=numericValue().toInt()
if(integer in -128..127)
RuntimeValue(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt())
DataType.WORD -> {
val integer=numericValue().toInt()
if(integer in -32768..32767)
RuntimeValue(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
open fun iterator(): Iterator<Number> {
return when (type) {
in StringDatatypes -> {
Petscii.encodePetscii(str!!, true).iterator()
}
in ArrayDatatypes -> {
array!!.iterator()
}
else -> throw IllegalArgumentException("cannot iterate over $this")
}
}
}
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, array=range.toList().toTypedArray()) {
override fun iterator(): Iterator<Number> {
return range.iterator()
}
}

View File

@ -1,962 +0,0 @@
package prog8.vm.astvm
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import java.awt.EventQueue
import java.io.CharConversionException
import java.util.*
import kotlin.NoSuchElementException
import kotlin.concurrent.fixedRateTimer
import kotlin.math.*
import kotlin.random.Random
class VmExecutionException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg)
class VmBreakpointException : Exception("breakpoint")
class StatusFlags {
var carry: Boolean = false
var zero: Boolean = true
var negative: Boolean = false
var irqd: Boolean = false
private fun setFlags(value: NumericLiteralValue?) {
if (value != null) {
when (value.type) {
DataType.UBYTE -> {
val v = value.number.toInt()
negative = v > 127
zero = v == 0
}
DataType.BYTE -> {
val v = value.number.toInt()
negative = v < 0
zero = v == 0
}
DataType.UWORD -> {
val v = value.number.toInt()
negative = v > 32767
zero = v == 0
}
DataType.WORD -> {
val v = value.number.toInt()
negative = v < 0
zero = v == 0
}
DataType.FLOAT -> {
val flt = value.number.toDouble()
negative = flt < 0.0
zero = flt == 0.0
}
else -> {
// no flags for non-numeric type
}
}
}
}
}
class RuntimeVariables {
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) {
val where = vars.getValue(scope)
where[name] = initialValue
vars[scope] = where
}
fun defineMemory(scope: INameScope, name: String, address: Int) {
val where = memvars.getValue(scope)
where[name] = address
memvars[scope] = where
}
fun set(scope: INameScope, name: String, value: RuntimeValue) {
val where = vars.getValue(scope)
val existing = where[name]
if(existing==null) {
if(memvars.getValue(scope)[name]!=null)
throw NoSuchElementException("this is a memory mapped var, not a normal var: ${scope.name}.$name")
throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
}
if(existing.type!=value.type)
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
where[name] = value
vars[scope] = where
}
fun get(scope: INameScope, name: String): RuntimeValue {
val where = vars.getValue(scope)
return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
}
fun getMemoryAddress(scope: INameScope, name: String): Int {
val where = memvars.getValue(scope)
return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
}
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
val v1 = get(scope1, name1)
val v2 = get(scope2, name2)
set(scope1, name1, v2)
set(scope2, name2, v1)
}
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValue>>().withDefault { mutableMapOf() }
private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() }
}
class AstVm(val program: Program) {
val mem = Memory(::memread, ::memwrite)
val statusflags = StatusFlags()
private var dialog = ScreenDialog("AstVM")
var instructionCounter = 0
val bootTime = System.currentTimeMillis()
var rtcOffset = bootTime
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
private val registerXsave = Stack<RuntimeValue>()
private val registerYsave = Stack<RuntimeValue>()
private val registerAsave = Stack<RuntimeValue>()
init {
// observe the jiffyclock and screen matrix
mem.observe(0xa0, 0xa1, 0xa2)
for(i in 1024..2023)
mem.observe(i)
dialog.requestFocusInWindow()
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
}
fixedRateTimer("60hz-irq", true, period=1000/60) {
irq(this.scheduledExecutionTime())
}
}
fun memread(address: Int, value: Short): Short {
// println("MEM READ $address -> $value")
return value
}
fun memwrite(address: Int, value: Short): Short {
if(address==0xa0 || address==0xa1 || address==0xa2) {
// a write to the jiffy clock, update the clock offset for the irq
val timeHi = if(address==0xa0) value else mem.getUByte_DMA(0xa0)
val timeMid = if(address==0xa1) value else mem.getUByte_DMA(0xa1)
val timeLo = if(address==0xa2) value else mem.getUByte_DMA(0xa2)
val jiffies = (timeHi.toInt() shl 16) + (timeMid.toInt() shl 8) + timeLo
rtcOffset = bootTime - (jiffies*1000/60)
}
if(address in 1024..2023) {
// write to the screen matrix
val scraddr = address-1024
dialog.canvas.setChar(scraddr % 40, scraddr / 40, value, 1)
}
return value
}
fun run() {
try {
val init = VariablesCreator(runtimeVariables, program.heap)
init.visit(program)
// initialize all global variables
for (m in program.modules) {
for (b in m.statements.filterIsInstance<Block>()) {
for (s in b.statements.filterIsInstance<Subroutine>()) {
if (s.name == initvarsSubName) {
try {
executeSubroutine(s, emptyList(), null)
} catch (x: LoopControlReturn) {
// regular return
}
}
}
}
}
var entrypoint: Subroutine? = program.entrypoint() ?: throw VmTerminationException("no valid entrypoint found")
var startlabel: Label? = null
while(entrypoint!=null) {
try {
executeSubroutine(entrypoint, emptyList(), startlabel)
entrypoint = null
} catch (rx: LoopControlReturn) {
// regular return
} catch (jx: LoopControlJump) {
if (jx.address != null)
throw VmTerminationException("doesn't support jumping to machine address ${jx.address}")
when {
jx.generatedLabel != null -> {
val label = entrypoint.getLabelOrVariable(jx.generatedLabel) as Label
TODO("generatedlabel $label")
}
jx.identifier != null -> {
when (val jumptarget = entrypoint.lookup(jx.identifier.nameInSource, jx.identifier.parent)) {
is Label -> {
startlabel = jumptarget
entrypoint = jumptarget.definingSubroutine()
}
is Subroutine -> entrypoint = jumptarget
else -> throw VmTerminationException("weird jump target $jumptarget")
}
}
else -> throw VmTerminationException("unspecified jump target")
}
}
}
dialog.canvas.printText("\n<program ended>", true)
println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) {
println("Execution halted: ${tx.message}")
} catch (xx: VmExecutionException) {
println("Execution error: ${xx.message}")
throw xx
}
}
private fun irq(timeStamp: Long) {
// 60hz IRQ handling
if(statusflags.irqd)
return // interrupt is disabled
var jiffies = (timeStamp-rtcOffset)*60/1000
if(jiffies>24*3600*60-1) {
jiffies = 0
rtcOffset = timeStamp
}
// update the C-64 60hz jiffy clock in the ZP addresses:
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort())
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort())
mem.setUByte_DMA(0x00a2, (jiffies and 255).toShort())
}
private val runtimeVariables = RuntimeVariables()
private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, ::performBuiltinFunction, ::executeSubroutine)
class LoopControlBreak : Exception()
class LoopControlContinue : Exception()
class LoopControlReturn(val returnvalue: RuntimeValue?) : Exception()
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startAtLabel: Label?=null): RuntimeValue? {
if(sub.isAsmSubroutine) {
return performSyscall(sub, arguments)
}
if (sub.statements.isEmpty())
throw VmTerminationException("scope contains no statements: $sub")
if (arguments.size != sub.parameters.size)
throw VmTerminationException("number of args doesn't match number of required parameters")
for (arg in sub.parameters.zip(arguments)) {
val idref = IdentifierReference(listOf(arg.first.name), sub.position)
performAssignment(AssignTarget(null, idref, null, null, idref.position),
arg.second, sub.statements.first(), evalCtx)
}
val statements = sub.statements.iterator()
if(startAtLabel!=null) {
do {
val stmt = statements.next()
} while(stmt!==startAtLabel)
}
try {
while(statements.hasNext()) {
val s = statements.next()
try {
executeStatement(sub, s)
}
catch (b: VmBreakpointException) {
print("BREAKPOINT HIT at ${s.position} - Press enter to continue:")
readLine()
}
}
} catch (r: LoopControlReturn) {
return r.returnvalue
}
throw VmTerminationException("instruction pointer overflow, is a return missing? $sub")
}
internal fun executeAnonymousScope(scope: INameScope) {
for (s in scope.statements) {
executeStatement(scope, s)
}
}
private fun executeStatement(sub: INameScope, stmt: Statement) {
instructionCounter++
if (instructionCounter % 200 == 0)
Thread.sleep(1)
when (stmt) {
is NopStatement, is Label, is Subroutine -> {
// do nothing, skip this instruction
}
is Directive -> {
if (stmt.directive == "%breakpoint")
throw VmBreakpointException()
else if (stmt.directive == "%asm")
throw VmExecutionException("can't execute assembly code")
}
is VarDecl -> {
// should have been defined already when the program started
}
is FunctionCallStatement -> {
val target = stmt.target.targetStatement(program.namespace)
when (target) {
is Subroutine -> {
val args = evaluate(stmt.arglist)
if (target.isAsmSubroutine) {
performSyscall(target, args)
} else {
executeSubroutine(target, args, null)
// any return value(s) are discarded
}
}
is BuiltinFunctionStatementPlaceholder -> {
if(target.name=="swap") {
// swap cannot be implemented as a function, so inline it here
executeSwap(stmt)
} else {
val args = evaluate(stmt.arglist)
performBuiltinFunction(target.name, args, statusflags)
}
}
else -> {
TODO("weird call $target")
}
}
}
is Return -> {
val value =
if(stmt.value==null)
null
else
evaluate(stmt.value!!, evalCtx)
throw LoopControlReturn(value)
}
is Continue -> throw LoopControlContinue()
is Break -> throw LoopControlBreak()
is Assignment -> {
if (stmt.aug_op != null)
throw VmExecutionException("augmented assignment should have been converted into regular one $stmt")
val value = evaluate(stmt.value, evalCtx)
performAssignment(stmt.target, value, stmt, evalCtx)
}
is PostIncrDecr -> {
when {
stmt.target.identifier != null -> {
val ident = stmt.definingScope().lookup(stmt.target.identifier!!.nameInSource, stmt) as VarDecl
val identScope = ident.definingScope()
when(ident.type){
VarDeclType.VAR -> {
var value = runtimeVariables.get(identScope, ident.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(identScope, ident.name, value)
}
VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.number.toInt()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
VarDeclType.CONST -> throw VmExecutionException("can't be const")
}
}
stmt.target.memoryAddress != null -> {
val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
stmt.target.arrayindexed != null -> {
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
val elementType = stmt.target.arrayindexed!!.inferType(program)
if(!elementType.isKnown)
throw VmExecutionException("unknown/void elt type")
var value = RuntimeValue(elementType.typeOrElse(DataType.BYTE), arrayvalue.array!![index].toInt())
value = when {
stmt.operator == "++" -> value.inc()
stmt.operator == "--" -> value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
arrayvalue.array[index] = value.numericValue()
}
stmt.target.register != null -> {
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
}
else -> throw VmExecutionException("empty postincrdecr? $stmt")
}
}
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
is InlineAssembly -> {
if (sub is Subroutine) {
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) }
performSyscall(sub, args)
throw LoopControlReturn(null)
}
throw VmExecutionException("can't execute inline assembly in $sub")
}
is AnonymousScope -> executeAnonymousScope(stmt)
is IfStatement -> {
val condition = evaluate(stmt.condition, evalCtx)
if (condition.asBoolean)
executeAnonymousScope(stmt.truepart)
else
executeAnonymousScope(stmt.elsepart)
}
is BranchStatement -> {
when(stmt.condition) {
BranchCondition.CS -> if(statusflags.carry) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.CC -> if(!statusflags.carry) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.EQ, BranchCondition.Z -> if(statusflags.zero) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.NE, BranchCondition.NZ -> if(statusflags.zero) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.MI, BranchCondition.NEG -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.PL, BranchCondition.POS -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.VS, BranchCondition.VC -> TODO("overflow status")
}
}
is ForLoop -> {
val iterable = evaluate(stmt.iterable, evalCtx)
if (iterable.type !in IterableDatatypes && iterable !is RuntimeValueRange)
throw VmExecutionException("can only iterate over an iterable value: $stmt")
val loopvarDt: DataType
val loopvar: IdentifierReference
if (stmt.loopRegister != null) {
loopvarDt = DataType.UBYTE
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else {
val dt = stmt.loopVar!!.inferType(program)
loopvarDt = dt.typeOrElse(DataType.UBYTE)
loopvar = stmt.loopVar!!
}
val iterator = iterable.iterator()
for (loopvalue in iterator) {
try {
oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
}
}
is WhileLoop -> {
var condition = evaluate(stmt.condition, evalCtx)
while (condition.asBoolean) {
try {
executeAnonymousScope(stmt.body)
condition = evaluate(stmt.condition, evalCtx)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
}
}
is RepeatLoop -> {
do {
val condition = evaluate(stmt.untilCondition, evalCtx)
try {
executeAnonymousScope(stmt.body)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
} while (!condition.asBoolean)
}
is WhenStatement -> {
val condition=evaluate(stmt.condition, evalCtx)
for(choice in stmt.choices) {
if(choice.values==null) {
// the 'else' choice
executeAnonymousScope(choice.statements)
break
} else {
val value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val rtval = RuntimeValue.fromLv(value)
if(condition==rtval) {
executeAnonymousScope(choice.statements)
break
}
}
}
}
else -> {
TODO("implement $stmt")
}
}
}
private fun executeSwap(swap: FunctionCallStatement) {
val v1 = swap.arglist[0]
val v2 = swap.arglist[1]
val value1 = evaluate(v1, evalCtx)
val value2 = evaluate(v2, evalCtx)
val target1 = AssignTarget.fromExpr(v1)
val target2 = AssignTarget.fromExpr(v2)
performAssignment(target1, value2, swap, evalCtx)
performAssignment(target2, value1, swap, evalCtx)
}
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: Statement, evalCtx: EvalContext) {
val targetIdent = target.identifier
val targetArrayIndexed = target.arrayindexed
when {
targetIdent != null -> {
val decl = contextStmt.definingScope().lookup(targetIdent.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target $targetIdent")
if (decl.type == VarDeclType.MEMORY) {
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
when (decl.datatype) {
DataType.UBYTE -> mem.setUByte(address, value.byteval!!)
DataType.BYTE -> mem.setSByte(address, value.byteval!!)
DataType.UWORD -> mem.setUWord(address, value.wordval!!)
DataType.WORD -> mem.setSWord(address, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address, value.floatval!!)
DataType.STR -> mem.setString(address, value.str!!)
DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
else -> throw VmExecutionException("weird memaddress type $decl")
}
} else
runtimeVariables.set(decl.definingScope(), decl.name, value)
}
target.memoryAddress != null -> {
val address = evaluate(target.memoryAddress.addressExpression, evalCtx).wordval!!
evalCtx.mem.setUByte(address, value.byteval!!)
}
targetArrayIndexed != null -> {
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) {
val array = evaluate(targetArrayIndexed.identifier, evalCtx)
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx)
when (array.type) {
DataType.ARRAY_UB -> {
if (value.type != DataType.UBYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_B -> {
if (value.type != DataType.BYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_UW -> {
if (value.type != DataType.UWORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_W -> {
if (value.type != DataType.WORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_F -> {
if (value.type != DataType.FLOAT)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.STR, DataType.STR_S -> {
if (value.type !in ByteDatatypes)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
else -> throw VmExecutionException("strange array type ${array.type}")
}
if (array.type in ArrayDatatypes)
array.array!![index.integerValue()] = value.numericValue()
else if (array.type in StringDatatypes) {
val indexInt = index.integerValue()
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr)
val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
val identScope = ident.definingScope()
program.heap.update(array.heapId!!, newstr)
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
}
}
else {
val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue()
val elementType = targetArrayIndexed.inferType(program)
if(!elementType.isKnown)
throw VmExecutionException("unknown/void array elt type $targetArrayIndexed")
when(elementType.typeOrElse(DataType.UBYTE)) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address+index* MachineDefinition.Mflpt5.MemorySize, value.floatval!!)
else -> throw VmExecutionException("strange array elt type $elementType")
}
}
}
target.register != null -> {
runtimeVariables.set(program.namespace, target.register.name, value)
}
else -> TODO("assign $target")
}
}
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
// assign the new loop value to the loopvar, and run the code
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
executeAnonymousScope(stmt.body)
}
private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? {
var result: RuntimeValue? = null
when (sub.scopedname) {
"c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if (args[0].wordval != null) {
val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, true)
} else
dialog.canvas.printText(args[0].str!!, true)
}
"c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true)
}
"c64scr.print_ub0" -> {
dialog.canvas.printText("%03d".format(args[0].byteval!!), true)
}
"c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true)
}
"c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true)
}
"c64scr.print_uw0" -> {
dialog.canvas.printText("%05d".format(args[0].wordval!!), true)
}
"c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true)
}
"c64scr.print_ubhex" -> {
val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
}
"c64scr.print_uwhex" -> {
val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
}
"c64scr.print_uwbin" -> {
val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
}
"c64scr.print_ubbin" -> {
val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
}
"c64scr.clear_screenchars" -> {
dialog.canvas.clearScreen(6)
}
"c64scr.clear_screen" -> {
dialog.canvas.clearScreen(args[0].integerValue().toShort())
}
"c64scr.setcc" -> {
dialog.canvas.setChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort())
}
"c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
}
"c64scr.input_chars" -> {
val input=mutableListOf<Char>()
for(i in 0 until 80) {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
if(char=='\n')
break
else {
input.add(char)
val printChar = try {
Petscii.encodePetscii("" + char, true).first()
} catch (cv: CharConversionException) {
0x3f.toShort()
}
dialog.canvas.printPetscii(printChar)
}
}
val inputStr = input.joinToString("")
val heapId = args[0].wordval!!
val origStr = program.heap.get(heapId).str!!
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length)
program.heap.update(heapId, paddedStr)
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
}
"c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), false)
}
"c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!)
}
"c64.CLEARSCR" -> {
dialog.canvas.clearScreen(6)
}
"c64.CHRIN" -> {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
result = RuntimeValue(DataType.UBYTE, char.toShort())
}
"c64utils.str2uword" -> {
val heapId = args[0].wordval!!
val argString = program.heap.get(heapId).str!!
val numericpart = argString.takeWhile { it.isDigit() }
result = RuntimeValue(DataType.UWORD, numericpart.toInt() and 65535)
}
else -> TODO("syscall ${sub.scopedname} $sub")
}
return result
}
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> throw VmExecutionException("strange abs type ${args[0]}")
}
}
"max" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max())
}
"min" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
}
"sum" -> {
val sum = args.single().array!!.map { it.toDouble() }.sum()
when (args[0].type) {
DataType.ARRAY_UB -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_B -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, sum)
else -> throw VmExecutionException("weird sum type ${args[0]}")
}
}
"any" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = IntegerOrAddressOf(value, null)
}
null
}
"memsetw" -> {
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = IntegerOrAddressOf(value and 255, null)
target[i * 2 + 1] = IntegerOrAddressOf(value ushr 8, null)
}
null
}
"memcopy" -> {
val sourceHeapId = args[0].wordval!!
val destHeapId = args[1].wordval!!
val source = program.heap.get(sourceHeapId).array!!
val dest = program.heap.get(destHeapId).array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name))
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name))
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name))
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
runtimeVariables.set(program.namespace, Register.A.name, registerAsave.pop())
runtimeVariables.set(program.namespace, Register.X.name, registerXsave.pop())
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
null
}
else -> TODO("builtin function $name")
}
}
}

View File

@ -1,170 +0,0 @@
package prog8.vm.astvm
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue?
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
val runtimeVars: RuntimeVariables,
val performBuiltinFunction: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller)
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program)
if(constval!=null)
return RuntimeValue.fromLv(constval)
when(expr) {
is NumericLiteralValue -> return RuntimeValue.fromLv(expr)
is StringLiteralValue -> return RuntimeValue.fromLv(expr, ctx.program.heap)
is ArrayLiteralValue -> return RuntimeValue.fromLv(expr, ctx.program.heap)
is PrefixExpression -> {
return when(expr.operator) {
"-" -> evaluate(expr.expression, ctx).neg()
"~" -> evaluate(expr.expression, ctx).inv()
"not" -> evaluate(expr.expression, ctx).not()
// unary '+' should have been optimized away
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
}
}
is BinaryExpression -> {
val left = evaluate(expr.left, ctx)
val right = evaluate(expr.right, ctx)
return when(expr.operator) {
"<" -> RuntimeValue(DataType.UBYTE, if (left < right) 1 else 0)
"<=" -> RuntimeValue(DataType.UBYTE, if (left <= right) 1 else 0)
">" -> RuntimeValue(DataType.UBYTE, if (left > right) 1 else 0)
">=" -> RuntimeValue(DataType.UBYTE, if (left >= right) 1 else 0)
"==" -> RuntimeValue(DataType.UBYTE, if (left == right) 1 else 0)
"!=" -> RuntimeValue(DataType.UBYTE, if (left != right) 1 else 0)
"+" -> left.add(right)
"-" -> left.sub(right)
"*" -> left.mul(right)
"/" -> left.div(right)
"**" -> left.pow(right)
"<<" -> {
var result = left
repeat(right.integerValue()) {result = result.shl()}
result
}
">>" -> {
var result = left
repeat(right.integerValue()) {result = result.shr()}
result
}
"%" -> left.remainder(right)
"|" -> left.bitor(right)
"&" -> left.bitand(right)
"^" -> left.bitxor(right)
"and" -> left.and(right)
"or" -> left.or(right)
"xor" -> left.xor(right)
else -> throw VmExecutionException("unsupported operator "+expr.operator)
}
}
is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, ctx)
val index = evaluate(expr.arrayspec.index, ctx)
val value = array.array!![index.integerValue()]
return RuntimeValue(ArrayElementTypes.getValue(array.type), value)
}
is TypecastExpression -> {
return evaluate(expr.expression, ctx).cast(expr.type)
}
is AddressOf -> {
// we support: address of heap var -> the heap id
return try {
val heapId = expr.identifier.heapId(ctx.program.namespace)
RuntimeValue(DataType.UWORD, heapId)
} catch( f: FatalAstException) {
// fallback: use the hash of the name, so we have at least *a* value...
val address = expr.identifier.hashCode() and 65535
RuntimeValue(DataType.UWORD, address)
}
}
is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!!
return RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
}
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
is IdentifierReference -> {
val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) {
when {
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
else -> throw VmExecutionException("unexpected datatype $variable")
}
}
}
} else
throw VmExecutionException("weird identifier reference $variable")
}
is FunctionCall -> {
val sub = expr.target.targetStatement(ctx.program.namespace)
val args = expr.arglist.map { evaluate(it, ctx) }
return when(sub) {
is Subroutine -> {
val result = ctx.executeSubroutine(sub, args, null)
?: throw VmExecutionException("expected a result from functioncall $expr")
result
}
is BuiltinFunctionStatementPlaceholder -> {
val result = ctx.performBuiltinFunction(sub.name, args, ctx.statusflags)
?: throw VmExecutionException("expected 1 result from functioncall $expr")
result
}
else -> {
throw VmExecutionException("unimplemented function call target $sub")
}
}
}
is RangeExpr -> {
val cRange = expr.toConstantIntegerRange()
if(cRange!=null) {
val dt = expr.inferType(ctx.program)
if(dt.isKnown)
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
else
throw VmExecutionException("couldn't determine datatype")
}
val fromVal = evaluate(expr.from, ctx).integerValue()
val toVal = evaluate(expr.to, ctx).integerValue()
val stepVal = evaluate(expr.step, ctx).integerValue()
val range = makeRange(fromVal, toVal, stepVal)
val dt = expr.inferType(ctx.program)
if(dt.isKnown)
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), range)
else
throw VmExecutionException("couldn't determine datatype")
}
else -> {
throw VmExecutionException("unimplemented expression node $expr")
}
}
}

View File

@ -1,144 +0,0 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
class Memory(private val readObserver: (address: Int, value: Short) -> Short,
private val writeObserver: (address: Int, value: Short) -> Short)
{
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
private val observed = BooleanArray(65536) // what addresses are observed
fun observe(vararg address: Int) {
address.forEach { observed[it]=true }
}
fun getUByte(address: Int): Short {
return if(observed[address]) readObserver(address, mem[address])
else mem[address]
}
fun getUByte_DMA(address: Int): Short {
return mem[address]
}
fun getSByte(address: Int): Short {
val ubyte = getUByte(address)
return if(ubyte <= 127) ubyte
else (-((ubyte.toInt() xor 255)+1)).toShort() // 2's complement
}
fun setUByte(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value")
mem[address] =
if(observed[address]) writeObserver(address, value)
else value
}
fun setUByte_DMA(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value")
mem[address] = value
}
fun setSByte(address: Int, value: Short) {
if(value !in -128..127) throw VmExecutionException("byte value out of range $value")
val ubyte =
if(value>=0) value
else ((abs(value.toInt()) xor 255)+1).toShort() // 2's complement
setUByte(address, ubyte)
}
fun getUWord(address: Int): Int {
return getUByte(address) + 256*getUByte(address+1)
}
fun getSWord(address: Int): Int {
val uword = getUWord(address)
if(uword <= 32767)
return uword
return -((uword xor 65535)+1) // 2's complement
}
fun setUWord(address: Int, value: Int) {
if(value !in 0..65535)
throw VmExecutionException("uword value out of range $value")
setUByte(address, value.and(255).toShort())
setUByte(address+1, (value / 256).toShort())
}
fun setSWord(address: Int, value: Int) {
if(value !in -32768..32767) throw VmExecutionException("word value out of range $value")
if(value>=0)
setUWord(address, value)
else
setUWord(address, (abs(value) xor 65535)+1) // 2's complement
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2)
setUByte(address+3, mflpt5.b3)
setUByte(address+4, mflpt5.b4)
}
fun getFloat(address: Int): Double {
return MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
getUByte(address + 3), getUByte(address + 4)).toDouble()
}
fun setString(address: Int, str: String) {
// lowercase PETSCII
val petscii = Petscii.encodePetscii(str, true)
var addr = address
for (c in petscii) setUByte(addr++, c)
setUByte(addr, 0)
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = getUByte(addr++)
if(byte==0.toShort()) break
petscii.add(byte)
}
return Petscii.decodePetscii(petscii, true)
}
fun clear() {
for(i in 0..65535) setUByte(i, 0)
}
fun copy(from: Int, to: Int, numbytes: Int) {
for(i in 0 until numbytes)
setUByte(to+i, getUByte(from+i))
}
fun getScreencodeString(strAddress: Int): String? {
// lowercase Screencodes
val screencodes = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = getUByte(addr++)
if(byte==0.toShort()) break
screencodes.add(byte)
}
return Petscii.decodeScreencode(screencodes, true)
}
fun setScreencodeString(address: Int, str: String) {
// lowercase screencodes
val screencodes = Petscii.encodeScreencode(str, true)
var addr = address
for (c in screencodes) setUByte(addr++, c)
setUByte(addr, 0)
}
}

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