Compare commits

...
371 Commits
Author SHA1 Message Date
Irmen de Jong 0f83dc6491 sponsorship link.
error sorting.
version.
2024-01-19 20:04:29 +01:00
Irmen de Jong cc22861719 Create FUNDING.yml 2024-01-19 19:26:41 +01:00
Irmen de Jong a14c192ea3 also other targets 2024-01-18 22:31:34 +01:00
Irmen de Jong b3d98be862 oops, removed a bit too much when getting rid of the noshortcircuit code 2024-01-18 22:00:57 +01:00
Irmen de Jong 43027a4728 IR: optimize rol ror 2024-01-18 21:51:44 +01:00
Irmen de Jong 03831a7394 added cx16.cpu_is_65816() 2024-01-18 19:30:18 +01:00
Irmen de Jong fdbbd181ea fixes for address-of uword pointer array expressions 2024-01-17 22:51:15 +01:00
Irmen de Jong 69075376dc get rid of the noshortcircuit fallback 2024-01-17 21:24:41 +01:00
Irmen de Jong 504d1440cc fixed rol(),rol2(),ror(),ror2() 2024-01-17 21:02:17 +01:00
OODLER 9e33b8b8da Added a couple of for examples using descending ranges. (#119) 2024-01-16 20:19:05 +01:00
Irmen de Jong 0cfcc5cd29 fix VM sgn() function for floats 2024-01-16 01:34:55 +01:00
Irmen de Jong e0de662f8e fix signed word bitshift right (>8 shifts) 2024-01-16 01:08:16 +01:00
Irmen de Jong 66a836d094 added support for reverse() on split word arrays 2024-01-16 00:52:09 +01:00
Irmen de Jong 80095f4962 added support for any() on split word arrays 2024-01-15 23:51:19 +01:00
Irmen de Jong 828d83dbef loadScaledArrayIndexIntoRegister(): useless type arg removed 2024-01-15 22:19:52 +01:00
Irmen de Jong 7de665d1e4 support for split wordarrays rol/ror and rol2/ror2
optimized rol and ror codegen

optimize/fix ror/ror2/rol/rol2 on word arrays
2024-01-15 03:22:37 +01:00
Irmen de Jong 0a356ba73a added containment check of float arrays 2024-01-14 14:14:09 +01:00
Irmen de Jong 41de8caa13 added sprites.set_mousepointer_image(), sprites.set_mousepointer_hand() and sprites.get_data_ptr() 2024-01-14 00:38:56 +01:00
Irmen de Jong 968609d06d IR: fix problems with symbol offsets and unused subroutines/chunks 2024-01-13 16:43:41 +01:00
Irmen de Jong 3b199a2a87 added cx16 example: automatons.
added debug mode and RTC to cx16 emulator launchers.
dt error details.
2024-01-12 21:40:30 +01:00
Irmen de Jong 0c1018ec61 dt error details 2024-01-12 17:34:19 +01:00
Irmen de Jong bc3f2db3de Fix call graph to no longer mark subroutines unused, that still get their variables referenced somewhere.
Revert palette.default_colors_16[] back to palette.set_default16.colors.
2024-01-11 22:12:01 +01:00
Irmen de Jong 06bedb7adb added palette.get_color() and palette.default_colors[] 2024-01-11 21:27:18 +01:00
Irmen de Jong 45a9751217 fix type of optimized lsb() / mkword() arguments when signed.
printast1 command line option now also works in case of compilation errors.
2024-01-10 23:57:44 +01:00
Irmen de Jong e8da62aa29 update Kotlin version and libs 2024-01-10 20:31:30 +01:00
Irmen de Jong ddb2ff4216 IR: use SCS opcode to set carry status flag into register 2024-01-09 23:46:27 +01:00
Irmen de Jong f27e3478b9 fix const value of AddressOf for certain types 2024-01-09 22:10:25 +01:00
Irmen de Jong 38dc7fb7bd IR: added SCC and SCS instructions 2024-01-09 01:41:37 +01:00
Irmen de Jong aa4cd13c31 where to place vscode syntax files. 2024-01-08 00:09:21 +01:00
Irmen de Jong 4a4b6c04a1 tweak common subexpression optimization for array lookups 2024-01-07 22:37:15 +01:00
Irmen de Jong 37fa3b34a2 tweak IR 2024-01-07 22:12:09 +01:00
Irmen de Jong f8084e7955 fix const replacement optimization error on memory mapped variable 2024-01-07 18:48:18 +01:00
Irmen de Jong 4d5119ce3e VM: also set N+Z flags on AND/OR/XOR instructions 2024-01-07 15:26:36 +01:00
Irmen de Jong d85c347a6c optimize /256 more, and fixed a unsigned byte word cast error 2024-01-07 02:34:05 +01:00
Irmen de Jong 7dd758a753 better optimize WORD & $xx00 , WORD & $00xx 2024-01-06 22:01:21 +01:00
Irmen de Jong 806654fc44 fix invalid const optimization with multiplication 2024-01-06 13:59:13 +01:00
Irmen de Jong 8e6b91cb9e some optimizations 2024-01-06 00:44:00 +01:00
Irmen de Jong 334e6dca28 added string.contains().
fixed string and array containment check for length 1.
2024-01-05 20:46:26 +01:00
Irmen de Jong f2daa17b92 tweak some not optimizations
cleanup IR typestring
2024-01-05 17:49:56 +01:00
Irmen de Jong 6d9fccacb1 boolean not expression tweaks and optimizations 2024-01-05 13:32:16 +01:00
Irmen de Jong 37638e7ed0 added Absorption laws optimization 2024-01-05 00:36:47 +01:00
Irmen de Jong 8a0e650511 apply De Morgan's laws for logical not, results in smaller code 2024-01-04 23:45:46 +01:00
Irmen de Jong 8ba5a0d90c tweak planet name display in starmap 2024-01-04 21:52:52 +01:00
Irmen de Jong bfd3edb617 fix expression evaluation bug where intermediate values were overwritten, yielding the wrong result 2024-01-04 21:04:11 +01:00
Irmen de Jong 56ba24962c fixed 'not not x' optimization to just 'x' 2024-01-04 15:02:21 +01:00
Irmen de Jong 19a2110ba2 fix exit() now actually correctly setting the return code in A
also, moved some cleanup stuff such as CLRCHN from exit() to the cleanup routine that is always called.
finally, also call the cleanup routine when  %option no_sysinit is used
2024-01-04 00:43:35 +01:00
Irmen de Jong 242a3eec63 fix data type difference error on range from and to values 2024-01-03 21:46:22 +01:00
Irmen de Jong fee46f2e54 todo 2024-01-03 15:06:27 +01:00
Irmen de Jong 6aed7e429a allow containment check in a range expression ("run time" range expression) 2024-01-03 01:17:13 +01:00
Irmen de Jong 517ea82b99 fixed todos in Ast printer 2024-01-01 23:52:03 +01:00
Irmen de Jong 99c29343de added -printast1 and -printast2 command line options 2024-01-01 22:48:19 +01:00
Irmen de Jong 892fa76883 remove debug output 2024-01-01 20:48:41 +01:00
Irmen de Jong d446b57d05 fix unittest 2024-01-01 16:11:50 +01:00
Irmen de Jong 0e086d788b removed chained comparisons again, because they caused invalid expression evaluations due to changed semantics. 2024-01-01 16:00:11 +01:00
Irmen de Jong 498841d45d remove binexpr operand rotation that didn't help much at all 2024-01-01 15:12:15 +01:00
Irmen de Jong d1f8ee1e56 replace most common subexpressions by a single temp variable 2024-01-01 14:57:24 +01:00
Irmen de Jong 07feb5c925 extra check 2023-12-31 17:04:28 +01:00
Irmen de Jong 75fd263e85 fix expericodegen crash related to shortcircuiting 2023-12-31 01:28:17 +01:00
Irmen de Jong 1e1f444cab cleanups.
also checked that value(12) < x < value(100) is indeed properly shortcircuited if x is 12 or less
2023-12-30 04:34:07 +01:00
Irmen de Jong 89cc7e5fa9 finalize short-circuit eval in IR codegen 2023-12-30 04:26:29 +01:00
Irmen de Jong 265e7aefbf clean up unused codegen for logical ops on words, also fix no-shortcircuit exception 2023-12-30 04:06:02 +01:00
Irmen de Jong 1c55a6c6dc shortcutting part one 2023-12-30 03:54:12 +01:00
Irmen de Jong 8f18b5b8a7 keep distinction between logical and bitwise boolean operators 2023-12-30 01:08:42 +01:00
Irmen de Jong f790182f0b adding short-circuit boolean expression evaluation (in IR codegen) also -noshortcircuit cli option 2023-12-30 01:08:41 +01:00
Irmen de Jong 813007a5d8 adjusted options of library modules 2023-12-29 22:21:44 +01:00
Irmen de Jong d03ff1e4d0 improved var -> const replacement, now done in constfolding already (fixes some obscure problems later on)
Also fixed some directive parenting errors
2023-12-29 19:48:40 +01:00
Irmen de Jong 932bbd0381 allow casting of byte<->ubyte and word<->uword 2023-12-29 16:23:24 +01:00
Irmen de Jong 01bd648cb2 added math.crc16() and math.crc32() 2023-12-29 08:00:02 +01:00
Irmen de Jong 779a5606a7 add unittest for aa%bb (without space) to be parsed correctly as modulo, not directive 2023-12-29 05:11:50 +01:00
Irmen de Jong ccc11e49d2 fix asmgen for uword shift right 8 or more bits 2023-12-29 05:06:09 +01:00
Irmen de Jong d28c994ecd directive really needs to be listed out in the parser otherwise it confuses it with % modulo :-(
Also fix missing const fold pass in optimizer
2023-12-29 03:45:20 +01:00
Irmen de Jong 5d88717f32 fix non-existing instructions txy/tyx, oops 2023-12-29 03:27:35 +01:00
Irmen de Jong e35cfd4971 get rid of the redundant 'f' suffix of several funtions in floats module (breaking change!)
Unfortunately a few routines (minf, maxf, clampf) remain unchanged, because removing the 'f' would make them clash with a builtin function.

floats.rndf -> floats.rnd
floats.parse_f -> floats.parse
floats.rndseedf -> floats.rndseed
floats.print_f -> floats.print
floats.str_f -> floats.tostr
2023-12-29 03:12:44 +01:00
Irmen de Jong bcc4bf5c2b almost forgot 2023-12-28 20:39:27 +01:00
Irmen de Jong a0594cbce3 const optimizer now knows about a bunch of library functions, such as math.* 2023-12-28 20:14:13 +01:00
Irmen de Jong 078bfefe41 clarify scoped names a bit more 2023-12-28 16:36:29 +01:00
Irmen de Jong 9c1b11d605 some WARN messages are now INFO 2023-12-28 14:20:47 +01:00
Irmen de Jong 44d82f9190 add unit tests 2023-12-28 13:30:07 +01:00
Irmen de Jong 37fcde30d6 constants have p8c_ prefix instead of p8v_ in the asm 2023-12-28 05:28:32 +01:00
Irmen de Jong 09c6cb4d6b replace unwritten vars by consts. Improved const eval.
Fixed some slight bugs in library code
2023-12-28 05:17:15 +01:00
Irmen de Jong b428343c2a tweak chained comparisons 2023-12-28 02:31:39 +01:00
Irmen de Jong dfce292294 allow chained comparisons i<x<j (desugared into: i<x and x<j) 2023-12-28 01:18:59 +01:00
Irmen de Jong 2b8f613a00 added %option ignore_unused to suppress warnings about unused vars and subs in that module/block.
Also improved error for invalid directive.
2023-12-26 23:37:59 +01:00
Irmen de Jong 2eb137618e refactor block options 2023-12-26 22:13:08 +01:00
Irmen de Jong 4bb2b8ca9b make isArray a computed property by simply checking the datatype 2023-12-26 19:58:08 +01:00
Irmen de Jong 5179562fb2 can be val 2023-12-26 19:39:02 +01:00
Irmen de Jong 0a4de45453 get rid of vardecl.declareddatatype 2023-12-26 19:33:58 +01:00
Irmen de Jong ffdc658dc8 type error tweaks 2023-12-26 18:49:01 +01:00
Irmen de Jong 7530f4407b ir tag change INLINEASM->ASM 2023-12-26 16:15:19 +01:00
Irmen de Jong 73864c8101 added -check command line option 2023-12-26 15:45:55 +01:00
Irmen de Jong f948917124 added floats.push() and floats.pop()
fixed vm pop.f
2023-12-26 15:19:49 +01:00
Irmen de Jong 0d44492086 push,pushw,pop and popw are no longer built-in functions but regular routines in sys 2023-12-26 14:47:31 +01:00
Irmen de Jong 38a22fbc99 allow %option no_symbol_prefixing also on module scope 2023-12-26 12:31:18 +01:00
Irmen de Jong 8ae435549d added -slabshigh N and -slabsgolden for memory() slabs 2023-12-23 20:45:30 +01:00
Irmen de Jong 9b113c0cbb added -varsgolden to put BSS into Golden Ram at $0400 2023-12-23 20:11:50 +01:00
Irmen de Jong 0e0fac8c4b BSSHIGHRAM_END more clearly defined (to be inclusive) 2023-12-23 19:05:06 +01:00
Irmen de Jong 4cd9bb8f99 allow Python-style negative array indexing to count from the end 2023-12-23 16:37:28 +01:00
Irmen de Jong ad9eaeafeb call now returns a word value 2023-12-22 22:24:11 +01:00
Irmen de Jong 6cd392909c added verafx.copy() routine for fast vram-to-vram copying ('blitting') 2023-12-22 17:52:43 +01:00
Irmen de Jong 49ec430592 cx16: added several word Vera-registers as memory-mapped vars as well 2023-12-21 00:28:09 +01:00
OODLER 09f3fbeb38 doc tweak, adding qualified name example for goto (#116) 2023-12-20 23:51:46 +01:00
OODLER e7698686fa fixed a glitch in the prog8/vtui example (#117)
When the program starts, before the screen is modified, save_rec
is called to capture the cx16 logo.

The fix was to not do the second call to save_rec until after
vtui is used to draw the boxes on the screen.

Before the this fix, the initial replacement issue was of the
logo and not the portion of the screen originally modified by
vtui.
2023-12-20 23:49:47 +01:00
Irmen de Jong 66d939df0d docs about new asm symbol prefixes 2023-12-20 22:37:46 +01:00
Irmen de Jong 6bc079c7b7 more asm symbol prefixing: variables with p8v_, subroutines with p8s_ etc
labels with p8l_ . All this to avoid symbol clashes in the generated assembly code.
Everything got its own distinguishing prefix so we're done with it once and for all and have only 1 breaking change moment.
2023-12-20 22:20:59 +01:00
Irmen de Jong 299419917e added symbol ambiguity error (variable vs block name for scoped symbols)
fixes #114
2023-12-20 00:28:15 +01:00
Irmen de Jong 69f6afe420 block names in asm now prefixed with p8b_ (instead of p8_)
as part of fixing var versus block symbol conflict handling
2023-12-19 23:00:20 +01:00
Irmen de Jong b7279a3d9e fix 'not in' parsing error
fixes #115
2023-12-19 19:49:25 +01:00
Irmen de Jong e14b854d7b cx16: added audio kernal routines example 2023-12-19 01:47:05 +01:00
Irmen de Jong 8bd7c601c0 cx16: added all remaining audio kernal routines. added the three x16edit kernal entry points. 2023-12-18 22:16:44 +01:00
Irmen de Jong 997288fa03 added cbm.CLEARST() to reset ST to 0 2023-12-18 01:20:24 +01:00
Irmen de Jong 0f26b39997 improve diskio error handling and device not found errors
for instance if you set drivenumber to 9 without having a second drive connected, it used to hang in various routines
2023-12-17 22:39:08 +01:00
Irmen de Jong ae66fcac1e added call builtin function for indirect JSR 2023-12-17 15:45:28 +01:00
Irmen de Jong 43944a94eb doc tweaks 2023-12-17 14:47:20 +01:00
Irmen de Jong eba0bde6f3 Merge branch 'optimize-st'
# Conflicts:
#	examples/test.p8
2023-12-17 02:11:01 +01:00
Irmen de Jong 4544af441b doc tweaks, explain str a bit more 2023-12-17 02:02:59 +01:00
Irmen de Jong a8be94de6b better error message when attempting to cast a float to integer 2023-12-15 22:28:06 +01:00
Irmen de Jong b24df31c2b IR: fix codegen for routines returning in CPU Status register flag 2023-12-14 21:16:14 +01:00
Irmen de Jong 332ba8ed7e don't give error when returning uword value in subroutine that returns STR 2023-12-14 02:48:21 +01:00
Irmen de Jong 58400f53bc optimize: flip if true/else blocks if the else block only contains a jump (inverting the condition) 2023-12-13 22:06:53 +01:00
Irmen de Jong 01c2112881 remove PtJump label, just use identifier with dummy 2023-12-13 04:16:49 +01:00
Irmen de Jong a546c2247d optimize if-else handling of asmsub boolean result in status flags 2023-12-13 04:03:21 +01:00
Irmen de Jong 0da9142009 asm: also work for asmsub that return N or Z flag (Carry already worked) 2023-12-13 02:11:15 +01:00
Irmen de Jong 796add0ee2 add string.isspace and string.isprint 2023-12-13 00:28:34 +01:00
Irmen de Jong 00b32f64e6 tweak for,sort,reverse st use 2023-12-12 20:26:45 +01:00
Irmen de Jong f97b3f23e2 optimize symbol table for IR 2023-12-12 19:26:27 +01:00
Irmen de Jong 08a079a96e concerns with in for strings 2023-12-11 21:15:48 +01:00
Irmen de Jong e98e951834 fix chained assignment and multi-vardecl RHS evaluation 2023-12-10 16:44:51 +01:00
Irmen de Jong 2668bf8519 fix void optimization issue 2023-12-09 21:48:22 +01:00
Irmen de Jong dd4c073e18 version 9.7 2023-12-09 18:54:54 +01:00
Irmen de Jong c7c72f00c7 document underscores in numeric literals for grouping 2023-12-09 14:07:42 +01:00
Irmen de Jong ef1c665b9a allow underscores for numerical grouping 2023-12-09 13:13:34 +01:00
Irmen de Jong d56565be25 fix multi-var decl 2023-12-09 12:32:41 +01:00
Irmen de Jong e076b3aedc fix multi-var decl in nested scopes 2023-12-09 12:07:09 +01:00
Irmen de Jong ae3b2ddf5f allow multi var declarations for floats too 2023-12-08 23:29:13 +01:00
Irmen de Jong 1bdc427d73 multi var declarations ubyte x,y,z 2023-12-08 22:18:21 +01:00
Irmen de Jong 6a639ce533 chained assignments x=y=z=42 2023-12-08 01:07:16 +01:00
Irmen de Jong d91ca8b197 vm: added floats.str_f() 2023-12-07 23:10:27 +01:00
Irmen de Jong a01c0a283d add check for memory() args to be const, added floats.str_f()
add check for memory() args to be const
2023-12-07 22:39:53 +01:00
Irmen de Jong 5c393091a0 unit test for %encoding 2023-12-07 21:54:01 +01:00
Frosty-J 01b680504b Add %encoding to syntax files (#113)
* IDEA `%encoding` syntax

* N++ `%encoding` syntax

* Vim `%encoding` syntax
2023-12-07 21:53:33 +01:00
Irmen de Jong 8e4319cd5a module directive %encoding to set the text encoding for that whole file (iso, petscii, etc.) 2023-12-06 23:54:08 +01:00
Irmen de Jong 5a776dd690 improve KotlinJavaRuntime library ref 2023-12-06 22:52:39 +01:00
Irmen de Jong cce08d95db unused subroutine warning only for main compilation module 2023-12-06 21:48:56 +01:00
Irmen de Jong 28c1b208c1 optimized calls for float *10 and +0.5 2023-12-06 01:18:07 +01:00
Irmen de Jong 3844bf1f72 fix string.isupper() 2023-12-05 23:52:43 +01:00
Irmen de Jong 745d192563 added floats.normalize() 2023-12-05 22:54:35 +01:00
Irmen de Jong ee782e92ac fix cast error and vm float parsing 2023-12-05 22:51:15 +01:00
Irmen de Jong afbc91d1fc added string.isdigit, islower, isupper, isletter 2023-12-05 22:50:20 +01:00
Irmen de Jong f998888d6d fix some unicode identifier issues 2023-12-05 17:38:23 +01:00
Irmen de Jong 7d8b42d63e allow Unicode letters in identifiers: things like 'knäckebröd' and 'π' are now valid identifiers. Added floats.π constant. 2023-12-05 01:36:54 +01:00
Irmen de Jong 6ebd4e821f improved docs about subroutine scoping, fix possible optimizer crash for inlined sub 2023-12-04 23:23:52 +01:00
Irmen de Jong d1806bfdc3 added remaining verafx registers 2023-12-03 22:15:29 +01:00
Irmen de Jong 1d2d7155da palette: changed some of the available presets. Also fix sizeof(array) crash. 2023-12-03 17:14:40 +01:00
Irmen de Jong b09e0a05bf some tweaks to errors about long integer literals 2023-12-03 02:45:26 +01:00
Irmen de Jong c609e982fe allow const expression intermediate values to be 32 bits integers to avoid needless overflow errors. 2023-12-03 01:48:12 +01:00
Irmen de Jong 2b227b43fe bmx: error for unsupported file version 2023-12-02 23:56:59 +01:00
Irmen de Jong 48f09f71ab fix TODO crash on uword[0] = uword[0] or 128 (byte register assign to word array) 2023-12-02 21:29:14 +01:00
Irmen de Jong ead8c59bda allow all character encodings on all compilation targets. 2023-12-02 20:59:50 +01:00
Irmen de Jong db52a9466c fix weird compiler warning for while 1 {..} 2023-12-02 20:24:45 +01:00
Irmen de Jong 1509de390e various fixes
print_f() no longer prints a leading space.
Better error message if using float in for loop.
Fix crash when using non-const as when choice value.
VM print_f() more closely resembles the CBM version.
2023-12-02 18:23:54 +01:00
Irmen de Jong 88a1aa4f3d fix invalid optimization for integers (X/C1)*C2 , only ok for floats because of int rounding 2023-12-01 23:17:49 +01:00
Irmen de Jong 172e78e8f2 ir: ignore empty chunks instead of crashing 2023-12-01 22:49:20 +01:00
Irmen de Jong 36bfef567d comments 2023-12-01 20:20:18 +01:00
Irmen de Jong e40ebd75a2 floats.parse_f uses kernal VAL if it's present 2023-11-30 23:50:50 +01:00
Irmen de Jong 992732f2cb bmx support to save partial screens ('stamps') 2023-11-30 22:17:57 +01:00
Irmen de Jong b58a3ba1bb added cx16 sprites.move , movex and movey routines to move sprite by deltas 2023-11-30 20:49:47 +01:00
Irmen de Jong afe521b0c9 simplify bmx loading 2023-11-29 21:57:17 +01:00
Irmen de Jong 5d9caef45f bmx can load "stamps" 2023-11-29 21:07:22 +01:00
Irmen de Jong 278e2f5605 preparing for working kernal FP VAL_1() call 2023-11-29 00:27:02 +01:00
Irmen de Jong 1e299bf360 better pokef() code 2023-11-28 23:04:27 +01:00
Irmen de Jong 8dfa0bc38c fix a compiler crash in certain vardecl initialization expressions 2023-11-28 21:01:58 +01:00
Irmen de Jong fde136fb7b bmx module only supports 320 or 640 image widths for now 2023-11-28 20:38:52 +01:00
Irmen de Jong ee4da1a757 fix floats.parse_f() to use new kernal routine address for VAL
gives error message if it detects issues f.ex. with new kernal version that moves the routine
2023-11-27 23:58:28 +01:00
Irmen de Jong ae2d96c455 added peekf and pokef builtin functions. Fixed sizeof() to allow number argument as well. 2023-11-27 23:36:02 +01:00
Irmen de Jong 6d8fbe0877 fixed float array indexing with an expression 2023-11-27 20:54:49 +01:00
Irmen de Jong 2fa1d8f2e8 fix vm string hash 2023-11-27 01:27:50 +01:00
Irmen de Jong 533090a68e fix expression result register of square, callfar, string compare functions in certain situations 2023-11-26 23:02:10 +01:00
Irmen de Jong 1dff59e1d6 added string.hash() 2023-11-26 22:14:08 +01:00
Irmen de Jong 44d232f52a optimize for x in something downto 0 2023-11-26 02:24:18 +01:00
Irmen de Jong 5f6cff739a fix bmx palette writing from buffer 2023-11-26 01:50:25 +01:00
Irmen de Jong 2764d235a9 optimizing for x in 0 to something 2023-11-25 21:37:27 +01:00
Irmen de Jong 45debff89f bmx: allow palette to be loaded into memory buffer instead of vram 2023-11-25 17:58:43 +01:00
Irmen de Jong c45fbe6310 continue stmt added 2023-11-25 01:14:35 +01:00
Irmen de Jong 9ef9c24388 IR: optimize redundant labels 2023-11-25 01:10:17 +01:00
Irmen de Jong 6a40f23578 cx16: added bmx library module and showbmx example 2023-11-24 23:39:05 +01:00
Irmen de Jong 6a0a6b4751 todo 2023-11-24 01:20:10 +01:00
Irmen de Jong 0bee6f6b41 cx16: reorder processing of IRQ handlers 2023-11-24 01:12:27 +01:00
Irmen de Jong 82a15b5a16 65C02 cpu: use TRB and TSB instructions for in-place AND/OR. 2023-11-24 00:50:52 +01:00
Irmen de Jong 11b7c4459e omission 2023-11-23 00:29:31 +01:00
Irmen de Jong 98570ac456 cx16: optimized sys.set_rasterline() 2023-11-23 00:02:04 +01:00
Irmen de Jong 1b2296ad5b move cx16 irq examples to new API, fix some bugs in the handler 2023-11-22 23:25:39 +01:00
Irmen de Jong 16851746d6 new X16 irq handler routines and examples 2023-11-22 20:03:21 +01:00
Irmen de Jong 935450a45f update kotest library 2023-11-22 18:40:07 +01:00
Irmen de Jong ba67fd318b renamed cx16.VERA_IRQ_LINE_L to VERA_IRQLINE_L and added VERA_SCANLINE_L, to align with official register naming.
Also added a multi-irq example for the X16 to show the updated irq handler semantics.
2023-11-22 18:36:24 +01:00
Irmen de Jong 08ac459a41 breaking change: sys.set_irq() and sys.set_rasterirq() no longer have useKernal parameter! The irq handler routine must return a boolean instead in the A register.
When it returns true it means run the system IRQ handler afterwards. When it returns false, the system handler is NOT ran afterwards.
2023-11-21 23:22:53 +01:00
Irmen de Jong a83e9d9a0a added sys.save_prog8_internals() and restore_prog8_internals() 2023-11-21 22:00:43 +01:00
Irmen de Jong 62d3f01948 fix name check in inline asm
this no longer removes a subroutine that is otherwise only called from inlined asm.
2023-11-21 01:26:50 +01:00
Irmen de Jong af5ca2d0b8 vm: treat floats as 64 bits doubles. 0.0 printed as "0". 2023-11-21 00:57:56 +01:00
Irmen de Jong ab4bcdf12d emudbg no longer clobbers r1 2023-11-20 00:20:48 +01:00
Irmen de Jong a6756d2cea removed diskio.set_drive(), just set the diskio.drivenumber variable directly
there already wasn't a getter
2023-11-19 22:15:56 +01:00
Irmen de Jong f81061dd42 error msg and comments 2023-11-18 01:03:34 +01:00
Irmen de Jong 8e2c304b3c txt.waitkey() now returns the key that was pressed 2023-11-17 20:31:19 +01:00
Irmen de Jong f21adaa3ef fix compiler error caused by removal of string symbol in txt.print() optimization 2023-11-17 19:51:48 +01:00
Irmen de Jong 2637939e62 cx16.vaddr_clone now leaves vera CTRL selected port intact 2023-11-17 19:22:23 +01:00
Irmen de Jong faf05582f8 improved cx16 emudbg library 2023-11-17 15:07:21 +01:00
Irmen de Jong 161c02ced3 message 2023-11-17 00:37:12 +01:00
Irmen de Jong ff8de8e42d removing redundant compares 2023-11-16 22:56:19 +01:00
Irmen de Jong 09d506194f note 2023-11-15 22:27:16 +01:00
Irmen de Jong 42db3085df improve the way %option merge works, you can now merge your own code with library code for instance. 2023-11-14 23:04:13 +01:00
Irmen de Jong ad14c88fde give error when using %option merge in module scope 2023-11-14 21:53:50 +01:00
Irmen de Jong 0c9daf6eaf fix compiler crash on ptrvar[n+1] = ptrvar[2] 2023-11-14 21:46:11 +01:00
Irmen de Jong 86c6530e46 palette: more accurate color conversion from 8 to 4 bits channels
set_rgb8(), color8to4(), channel8to4()
2023-11-14 20:40:48 +01:00
Irmen de Jong 159f80d629 next version 2023-11-14 19:06:47 +01:00
Irmen de Jong aa949165c7 diskio.f_open_w() error handling back to what it was before
Otherwise it eats the status message. Added comment that you have to check this manually to be sure if the call succeeded or not!
2023-11-12 21:14:06 +01:00
Irmen de Jong d22359b6e7 removed cx16.FB_cursor_position2() because it was only for use in the graphics module 2023-11-12 16:40:13 +01:00
Irmen de Jong d73709653d remove unused interned strings in the resulting code (for example from removed if/else blocks) 2023-11-12 05:28:24 +01:00
Irmen de Jong 405926e811 oops 2023-11-11 14:31:48 +01:00
Irmen de Jong 36758f41a4 fixed diskio.f_open_w() error handling, finally added f_seek_w() to be able to seek in files for writing. 2023-11-11 14:26:40 +01:00
Irmen de Jong 7ebc9c79cf added string.append()
cleanup redundant diskio prefixes
2023-11-10 23:53:59 +01:00
Irmen de Jong e0668b55b9 fix gfx2 safe_disc coloring 2023-11-10 01:08:13 +01:00
Irmen de Jong 76c09da961 make pokemon() be like poke, but also return the old value in the memory location. 2023-11-09 22:48:44 +01:00
Irmen de Jong 7e3b8c2c59 fix compiler crash on certain subroutine inlining attempts. 2023-11-09 21:16:12 +01:00
Irmen de Jong ecca854c7c Added cx16.edkeyvec and cx16.set_chrin_keyhandler(). mention the Github actions builds. 2023-11-09 01:03:31 +01:00
Irmen de Jong 3b0d7ea960 better const-evaluation of addressOf a memory mapped variable 2023-11-08 22:04:41 +01:00
Irmen de Jong f70fa42eac more accurate palette conversion 2023-11-08 01:33:55 +01:00
markjreed 5698de6cf4 feat: requirements.txt for convertsprite.py (#112) 2023-11-08 01:32:41 +01:00
Irmen de Jong c5a333a904 CX16: diskio.f_write() now uses fast MCIOUT block writes, including hiram bank boundary wrap-over 2023-11-08 01:12:49 +01:00
markjreed ff324955dd Feature/read cursor position (#111)
* feat: add ability to read cursor position on CBM machines

* feat: implement plot()/column() for atari target; add get_cursor(), get_column(), row(), and get_row()

* feat: implement wait_key() for Commodore targets; add get_cursor(), get_column(), row(), get_row()

* feat: really implement waitkey() on CBM targets

* fix: make waitkey void for compatibility with atari
2023-11-07 22:19:16 +01:00
Irmen de Jong 70436f5dca cx16.vpeek() use VERA_DATA0 instead of 1, to not cause ADDRSEL to be != 0 (interferes with kernal) 2023-11-07 22:09:53 +01:00
Irmen de Jong 31177a2b1b added sys.disable_caseswitch() and sys.enable_caseswitch() 2023-11-07 00:27:34 +01:00
Irmen de Jong 4de012fc49 added notes to textio about PETSCII vs Screencode encoding. 2023-11-06 23:18:24 +01:00
Irmen de Jong ee2888e744 verafx.mult/muls now return upper 16 bits of the result in r0 2023-11-06 21:55:58 +01:00
Irmen de Jong efe4df92dc optimize when with const value (remove other choices from code) 2023-11-06 00:08:07 +01:00
Irmen de Jong 723ab54f97 optimized all circle routines a little more. Added gfx2/monogfx safe_circle and safe_disc. Warning for when on const value. 2023-11-05 21:29:59 +01:00
Irmen de Jong d9389afc66 fix compiler crash on certain constant expressions 2023-11-05 13:59:08 +01:00
Irmen de Jong e7178ee496 optimized comparison with word variables 2023-11-05 00:20:12 +01:00
Irmen de Jong d5f35bb3fb added gfx2.init_mode() 2023-11-04 14:53:08 +01:00
Irmen de Jong 72f1a779f2 optimize monogfx.fill() and gfx2.fill(), also don't read outside screen area 2023-11-04 14:30:51 +01:00
Irmen de Jong 3277544295 optimize assigning word array value to byte variable 2023-11-04 00:33:50 +01:00
Irmen de Jong 98d2c64d5d fix assembly error for uword[3] @zp @split word_addrs 2023-11-03 00:39:43 +01:00
Irmen de Jong f68b46fc60 add a %zpallowed option to specify the range of zeropage register that can be used 2023-11-03 00:19:25 +01:00
Irmen de Jong d54ab856e7 fix parameter passing bug introduced recently (byte not converted to word) 2023-11-02 00:31:35 +01:00
Irmen de Jong 16b24fadea gfx2 future mode, upgrate to Kotlin 1.9.20 2023-11-01 23:18:44 +01:00
Irmen de Jong b3803cbdf1 more opportunities to use LDA(zp) instead of LDA(zp),Y on 65c02 2023-10-31 21:26:55 +01:00
Irmen de Jong 2ceaa25181 optimized code for (infrequently used) logical operations on word array 2023-10-29 23:41:34 +01:00
Irmen de Jong 513611c5a6 IR: using EXT more 2023-10-29 02:57:21 +01:00
Irmen de Jong 7ec4ba40ad optimize asmsub arg evaluation order and stack usage 2023-10-28 17:29:00 +02:00
Irmen de Jong 92374e122b IR: optimize concat with msb 0 into ext 2023-10-28 12:53:41 +02:00
Irmen de Jong 94f12732ab add math.diff() and math.diffw() 2023-10-27 22:36:43 +02:00
Irmen de Jong 0904712a00 remove last trace of getTempVar (arry index expression)
tiny optimization
2023-10-27 21:41:52 +02:00
Irmen de Jong 32becdbced add monogfx lib to virtual target 2023-10-24 00:16:25 +02:00
Irmen de Jong 34aa21f7d9 improve function call arg type casting 2023-10-22 22:33:35 +02:00
Irmen de Jong cc81dd7d3e remove useless close calls from diskio load 2023-10-22 17:24:05 +02:00
Irmen de Jong 335213b55f tweaks 2023-10-21 02:16:58 +02:00
Irmen de Jong 13ab4166c0 new kotest library version 2023-10-19 21:57:06 +02:00
Irmen de Jong 3dc5a0e7f8 some arrays can be in BSS 2023-10-18 23:59:37 +02:00
Irmen de Jong e15c5cde53 tiny fill() optimization 2023-10-18 23:11:16 +02:00
Irmen de Jong d88c09b098 fix signed byte to word casting issue uw = 8888 + (bb as ubyte) 2023-10-17 22:54:33 +02:00
Irmen de Jong 893b383bdf fix signed byte to word sign extension in assignment 2023-10-17 03:08:37 +02:00
Irmen de Jong dd7c9d62e6 remove assigment splitter, it now caused code bloat instead of more efficient code 2023-10-16 02:07:22 +02:00
Irmen de Jong 97c5c90eff fix codegen for var1>>=var2 and var1<<=var2 when var2 is zero 2023-10-16 00:04:21 +02:00
Irmen de Jong 1fb94e7a7b monogfx and gfx2: flood fill uses optimized horizontal line drawing 2023-10-15 23:19:11 +02:00
Irmen de Jong daca87c6d0 added -breakinstr compiler option 2023-10-15 21:55:09 +02:00
Irmen de Jong 203ec5fa46 implement taking address of array var with variable index 2023-10-15 20:24:48 +02:00
Irmen de Jong 9ea69c07b8 optimize word array reads with indexvar 2023-10-14 07:30:54 +02:00
Irmen de Jong 68539d6cc9 micro tweaks adpcm.p8 2023-10-13 00:55:56 +02:00
Irmen de Jong f75fd0811e restructure play-adpcm example code, stream-wav can now play stereo adpcm wavs 2023-10-11 17:37:42 +02:00
Irmen de Jong 836bc9d456 added verafx.available() 2023-10-10 22:12:21 +02:00
Irmen de Jong a37769aafe cx16 adpcm example is now able to decode and play stereo music as well as mono. 2023-10-10 02:41:20 +02:00
Irmen de Jong 68e62e4bd2 added cx16.MCIOUT() kernal call
correct case of several other cx16 kernal calls.

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

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
# These are supported funding model platforms
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: irmen
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+1 -1
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0-release-358" />
<option name="version" value="1.9.20" />
</component>
</project>
+16 -12
View File
@@ -1,19 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.22" />
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22.jar!/" />
</CLASSES>
<JAVADOC />
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-sources.jar!/" />
</SOURCES>
</library>
</component>
+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.12.0">
<properties maven-id="org.antlr:antlr4:4.13.1">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
+6 -6
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
-24
View File
@@ -1,24 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.property.jvm" type="repository">
<properties maven-id="io.kotest:kotest-property-jvm:5.6.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.6.2/kotest-property-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.4/rgxgen-1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+13 -13
View File
@@ -1,29 +1,29 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.6.2/kotest-runner-junit5-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.6.2/kotest-framework-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.6.2/kotest-framework-engine-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.157/classgraph-4.8.157.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.6.2/kotest-framework-discovery-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.6.2/kotest-extensions-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.6.2/kotest-framework-concurrency-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
+2 -2
View File
@@ -1,8 +1,8 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3 -3
View File
@@ -1,9 +1,9 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
<properties maven-id="org.slf4j:slf4j-simple:2.0.11" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.11/slf4j-simple-2.0.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.11/slf4j-api-2.0.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3
View File
@@ -16,6 +16,9 @@
</list>
</option>
</component>
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
+10
View File
@@ -1,3 +1,4 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/H2H6S0FFF)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
@@ -9,6 +10,13 @@ This is a structured programming language for the 8-bit 6502/6510/65c02 micropro
as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
**Want to buy me a coffee or a pizza perhaps?**
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen).
Documentation
-------------
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
@@ -74,6 +82,8 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- "pet32": Commodore PET (experimental)
- "atari": Atari 8 bit such as 800XL (experimental)
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
+2 -2
View File
@@ -32,10 +32,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+1 -4
View File
@@ -196,13 +196,10 @@ class StStaticVariable(name: String,
}
if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes)
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
require(length==onetimeInitializationArrayValue.size)
}
if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR)
+8 -5
View File
@@ -74,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialArray = makeInitialArray(value)
initialString = null
numElements = array.size
numElements = initialArray.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
@@ -126,7 +125,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtAddressOf -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name)
}
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
@@ -171,7 +174,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
// }
// VarDeclType.MEMORY -> {
// val numElements =
// if(decl.datatype in ArrayDatatypes)
// if(decl.isArray)
// decl.arraysize!!.constIndex()
// else null
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
+15 -8
View File
@@ -31,7 +31,10 @@ sealed class PtNode(val position: Position) {
}
class PtNodeGroup : PtNode(Position.DUMMY)
sealed interface IPtStatementContainer
class PtNodeGroup : PtNode(Position.DUMMY), IPtStatementContainer
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
@@ -64,26 +67,30 @@ class PtProgram(
children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): PtSub? =
allBlocks().firstOrNull { it.name == "main" || it.name=="p8_main" }
allBlocks().firstOrNull { it.name == "main" || it.name=="p8b_main" }
?.children
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8_start" || it.name=="p8_main.p8_start") } as PtSub?
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8s_start" || it.name=="p8b_main.p8s_start") } as PtSub?
}
class PtBlock(name: String,
val address: UInt?,
val library: Boolean,
val forceOutput: Boolean,
val noSymbolPrefixing: Boolean,
val alignment: BlockAlignment,
val source: SourceCode, // taken from the module the block is defined in.
val options: Options,
position: Position
) : PtNamedNode(name, position) {
) : PtNamedNode(name, position), IPtStatementContainer {
enum class BlockAlignment {
NONE,
WORD,
PAGE
}
class Options(val address: UInt? = null,
val forceOutput: Boolean = false,
val noSymbolPrefixing: Boolean = false,
val veraFxMuls: Boolean = false,
val ignoreUnused: Boolean = false,
val alignment: BlockAlignment = BlockAlignment.NONE)
}
+35 -8
View File
@@ -3,7 +3,7 @@ package prog8.code.ast
import prog8.code.core.*
import java.util.*
import kotlin.math.abs
import kotlin.math.round
import kotlin.math.truncate
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
@@ -24,9 +24,26 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
is PtAddressOf -> {
if(other !is PtAddressOf)
return false
if (other.type!==type || !(other.identifier isSameAs identifier))
return false
if(other.children.size!=children.size)
return false
if(children.size==1)
return true
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
}
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtBinaryExpression -> {
if(other !is PtBinaryExpression || other.operator!=operator)
false
else if(operator in AssociativeOperators)
(other.left isSameAs left && other.right isSameAs right) || (other.left isSameAs right && other.right isSameAs left)
else
other.left isSameAs left && other.right isSameAs right
}
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
@@ -62,7 +79,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtArray -> true
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
is PtBinaryExpression -> false
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
is PtBuiltinFunctionCall -> {
when (name) {
in arrayOf("msb", "lsb", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple() }
else -> false
}
}
is PtContainmentCheck -> false
is PtFunctionCall -> false
is PtIdentifier -> true
@@ -105,7 +127,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
val identifier: PtIdentifier
get() = children.single() as PtIdentifier
get() = children[0] as PtIdentifier
val arrayIndexExpr: PtExpression?
get() = if(children.size==2) children[1] as PtExpression else null
val isFromArrayElement: Boolean
get() = children.size==2
}
@@ -212,9 +239,9 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
if(type==DataType.BOOL)
throw IllegalArgumentException("bool should have become ubyte @$position")
if(type!=DataType.FLOAT) {
val rounded = round(number)
if (rounded != number)
throw IllegalArgumentException("refused rounding of float to avoid loss of precision @$position")
val trunc = truncate(number)
if (trunc != number)
throw IllegalArgumentException("refused truncating of float to avoid loss of precision @$position")
}
}
+16 -7
View File
@@ -53,15 +53,24 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
"goto ${node.identifier.name}"
else if(node.address!=null)
"goto ${node.address.toHex()}"
else if(node.generatedLabel!=null)
"goto ${node.generatedLabel}"
else
"???"
}
is PtAsmSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val params = node.parameters.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
}.joinToString(", ")
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.name.lowercase() }}")
val returns = if (node.returns.isEmpty()) "" else {
"-> ${node.returns.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second} @${register ?: statusflag}"}
.joinToString(", ")
}"
}
val str = if (node.inline) "inline " else ""
if(node.address==null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
@@ -70,8 +79,8 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
}
is PtBlock -> {
val addr = if(node.address==null) "" else "@${node.address.toHex()}"
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
val addr = if(node.options.address==null) "" else "@${node.options.address.toHex()}"
val align = if(node.options.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.options.alignment}"
"\nblock '${node.name}' $addr $align"
}
is PtConstant -> {
@@ -89,7 +98,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
}
is PtSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val params = node.parameters.map { "${it.type} ${it.name}" }.joinToString(", ")
var str = "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"
+2 -3
View File
@@ -23,7 +23,7 @@ class PtSub(
val parameters: List<PtSubroutineParameter>,
val returntype: DataType?,
position: Position
) : PtNamedNode(name, position), IPtSubroutine {
) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer {
init {
// params and return value should not be str
if(parameters.any{ it.type !in NumericDatatypes })
@@ -101,9 +101,8 @@ class PtIfElse(position: Position) : PtNode(position) {
}
class PtJump(val identifier: PtIdentifier?,
class PtJump(val identifier: PtIdentifier?, // note: even ad-hoc labels are wrapped as an Identifier to simplify code. Just use dummy type and position.
val address: UInt?,
val generatedLabel: String?,
position: Position) : PtNode(position) {
init {
identifier?.let {it.parent = this }
@@ -70,6 +70,8 @@ class FSignature(val pure: Boolean, // does it have side effects?
val BuiltinFunctions: Map<String, FSignature> = mapOf(
// this set of function have no return value and operate in-place:
"setlsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
"setmsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
@@ -78,7 +80,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
@@ -116,17 +120,16 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"peekf" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
"pokef" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"rsave" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
@@ -8,22 +8,32 @@ class CompilationOptions(val output: OutputType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
val zpReserved: List<UIntRange>,
val zpAllowed: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var warnSymbolShadowing: Boolean = false,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var includeSourcelines: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var breakpointCpuInstruction: Boolean = false,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
compTarget.machine.initializeMemoryAreas(this)
}
companion object {
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
}
}
+35 -19
View File
@@ -1,12 +1,13 @@
package prog8.code.core
enum class DataType {
UBYTE, // pass by value
BYTE, // pass by value
UWORD, // pass by value
WORD, // pass by value
FLOAT, // pass by value
BOOL, // pass by value
UBYTE, // pass by value 8 bits unsigned
BYTE, // pass by value 8 bits signed
UWORD, // pass by value 16 bits unsigned
WORD, // pass by value 16 bits signed
LONG, // pass by value 32 bits signed
FLOAT, // pass by value machine dependent
BOOL, // pass by value bit 0 of a 8 bit byte
STR, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
@@ -23,11 +24,12 @@ enum class DataType {
*/
infix fun isAssignableTo(targetType: DataType) =
when(this) {
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
LONG -> targetType.oneOf(LONG, FLOAT)
FLOAT -> targetType.oneOf(FLOAT)
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
@@ -41,7 +43,8 @@ enum class DataType {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this== STR && other== UWORD || this== UWORD && other== STR -> false
this == LONG -> other in ByteDatatypes+WordDatatypes
this == STR && other == UWORD || this == UWORD && other == STR -> false
else -> true
}
@@ -76,6 +79,13 @@ enum class RegisterOrPair {
companion object {
val names by lazy { values().map { it.toString()} }
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
CpuRegister.X -> X
CpuRegister.Y -> Y
}
}
}
fun asCpuRegister(): CpuRegister = when(this) {
@@ -110,17 +120,17 @@ enum class BranchCondition {
PL, // PL == POS
POS,
VS,
VC,
VC
}
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
@@ -150,8 +160,10 @@ val ElementToArrayTypes = mapOf(
DataType.WORD to DataType.ARRAY_W,
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F,
DataType.BOOL to DataType.ARRAY_BOOL
DataType.BOOL to DataType.ARRAY_BOOL,
DataType.STR to DataType.ARRAY_UW // array of str is just an array of pointers
)
val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
@@ -159,6 +171,10 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
enum class OutputType {
@@ -3,8 +3,6 @@ package prog8.code.core
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
val supportedEncodings: Set<Encoding>
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
@@ -3,11 +3,12 @@ package prog8.code.core
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun info(msg: String, position: Position)
fun undefined(symbol: List<String>, position: Position)
fun noErrors(): Boolean
fun report()
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
fun finalizeNumErrors(numErrors: Int, numWarnings: Int, numInfos: Int) {
if(numErrors>0)
throw ErrorsReportedException("There are $numErrors errors and $numWarnings warnings.")
throw ErrorsReportedException("There are $numErrors errors, $numWarnings warnings, and $numInfos infos.")
}
}
@@ -16,6 +16,8 @@ interface IMachineDefinition {
val PROGRAM_LOAD_ADDRESS : UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val BSSGOLDENRAM_START: UInt
val BSSGOLDENRAM_END: UInt
val cpu: CpuType
var zeropage: Zeropage
@@ -9,6 +9,8 @@ enum class Encoding(val prefix: String) {
}
interface IStringEncoding {
val defaultEncoding: Encoding
fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
}
@@ -42,6 +42,13 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
}
fun retainAllowed() {
synchronized(this) {
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun hasWordAvailable(): Boolean {
+1 -3
View File
@@ -1,12 +1,10 @@
package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
fun invertedComparisonOperator(operator: String) =
when (operator) {
+3 -2
View File
@@ -3,6 +3,7 @@ package prog8.code.core
import java.io.File
import java.io.IOException
import java.nio.file.Path
import java.text.Normalizer
import kotlin.io.path.Path
import kotlin.io.path.readText
@@ -94,7 +95,7 @@ sealed class SourceCode {
val normalized = path.normalize()
origin = relative(normalized).toString()
try {
text = normalized.readText()
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
name = normalized.toFile().nameWithoutExtension
} catch (nfx: java.nio.file.NoSuchFileException) {
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
@@ -126,7 +127,7 @@ sealed class SourceCode {
)
}
val stream = object {}.javaClass.getResourceAsStream(normalized)
text = stream!!.reader().use { it.readText() }
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
name = Path(pathString).toFile().nameWithoutExtension
}
}
@@ -0,0 +1,127 @@
package prog8.code.optimize
import prog8.code.ast.*
import prog8.code.core.*
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
if (!options.optimize)
return
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
// keep rolling
}
}
private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
fun recurse(node: PtNode, depth: Int) {
if(act(node, depth))
node.children.forEach { recurse(it, depth+1) }
}
recurse(root, 0)
}
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
val result = if(expr is PtBinaryExpression)
expr.type !in ByteDatatypes ||
!expr.left.isSimple() ||
!expr.right.isSimple() ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
true
else
!expr.isSimple()
return result
}
// for each Binaryexpression, recurse to find a common subexpression pair therein.
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtBinaryExpression) {
val subExpressions = mutableListOf<PtExpression>()
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
outer@for (first in subExpressions) {
for (second in subExpressions) {
if (first!==second && first isSameAs second) {
commons[node] = first to second
break@outer // do only 1 replacement at a time per binaryexpression
}
}
}
false
} else true
}
// replace common subexpressions by a temp variable that is assigned only once.
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
commons.forEach { binexpr, (occurrence1, occurrence2) ->
val (stmtContainer, stmt) = findContainingStatements(binexpr)
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
val containerScopedName = findScopeName(stmtContainer)
val tempvarName = "subexprvar_line${binexpr.position.line}_${binexpr.hashCode().toUInt()}"
// TODO: some tempvars could be reused, if they are from different lines
val datatype = occurrence1.type
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
occurrence1.parent.children[occurrence1idx] = singleReplacement1
singleReplacement1.parent = occurrence1.parent
occurrence2.parent.children[occurrence2idx] = singleReplacement2
singleReplacement2.parent = occurrence2.parent
val tempassign = PtAssignment(binexpr.position).also { assign ->
assign.add(PtAssignTarget(binexpr.position).also { tgt->
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
})
assign.add(occurrence1)
occurrence1.parent = assign
}
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
tempassign.parent = stmtContainer
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.DONTCARE, null, null, binexpr.position)
stmtContainer.add(0, tempvar)
tempvar.parent = stmtContainer
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
}
return commons.size
}
internal fun findScopeName(node: PtNode): String {
var parent=node
while(parent !is PtNamedNode)
parent = parent.parent
return parent.scopedName
}
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
var parent = node.parent
var child = node
while(true) {
if(parent is IPtStatementContainer) {
return parent to child
}
child=parent
parent=parent.parent
}
}
@@ -7,7 +7,6 @@ import prog8.code.target.atari.AtariMachineDefinition
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val supportedEncodings = setOf(Encoding.ATASCII)
override val defaultEncoding = Encoding.ATASCII
companion object {
@@ -19,7 +18,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
else -> throw IllegalArgumentException("invalid datatype")
}
}
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,7 +11,6 @@ import prog8.code.target.cx16.CX16MachineDefinition
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -10,6 +10,8 @@ import prog8.code.target.cbm.PetsciiEncoding
object Encoder: IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
@@ -0,0 +1,19 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.target.pet.PETMachineDefinition
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = PETMachineDefinition()
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "pet32"
}
}
+1 -2
View File
@@ -6,7 +6,6 @@ import prog8.code.target.virtual.VirtualMachineDefinition
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = VirtualMachineDefinition()
override val supportedEncodings = setOf(Encoding.ISO)
override val defaultEncoding = Encoding.ISO
companion object {
@@ -18,7 +17,7 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
else -> throw IllegalArgumentException("invalid datatype")
}
}
@@ -15,6 +15,8 @@ class AtariMachineDefinition: IMachineDefinition {
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
@@ -48,6 +48,7 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -17,6 +17,8 @@ class C128MachineDefinition: IMachineDefinition {
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
@@ -69,6 +69,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -17,7 +17,9 @@ class C64MachineDefinition: IMachineDefinition {
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = 0xd000u
override val BSSHIGHRAM_END = 0xcfffu
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
@@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
if(options.zeropage != ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -75,6 +75,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -9,7 +9,7 @@ internal object CbmMemorySizer: IMemSizer {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
else -> throw IllegalArgumentException("invalid datatype")
}
}
@@ -24,7 +24,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -152,7 +152,7 @@ object PetsciiEncoding {
'\uf113', //  0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', //  0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', //  0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', //  0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -283,7 +283,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -411,7 +411,7 @@ object PetsciiEncoding {
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', // 0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -1061,6 +1061,7 @@ object PetsciiEncoding {
'}' -> '├'
'|' -> '│'
'\\' -> '╲'
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
else -> chr
}
@@ -1076,7 +1077,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
else
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
}
}
}
@@ -1119,7 +1123,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
else
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
}
}
}
@@ -16,7 +16,9 @@ class CX16MachineDefinition: IMachineDefinition {
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xc000u // rom starts here.
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
override val BSSGOLDENRAM_START = 0x0400u
override val BSSGOLDENRAM_END = 0x07ffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
@@ -36,7 +38,7 @@ class CX16MachineDefinition: IMachineDefinition {
when(selectedEmulator) {
1 -> {
emulator = "x16emu"
extraArgs = emptyList()
extraArgs = listOf("-debug")
}
2 -> {
emulator = "box16"
@@ -49,7 +51,7 @@ class CX16MachineDefinition: IMachineDefinition {
}
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start()
@@ -47,8 +47,8 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
}
@@ -0,0 +1,57 @@
package prog8.code.target.pet
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class PETMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val BSSHIGHRAM_START = 0u
override val BSSHIGHRAM_END = 0u
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The pet target only supports the main emulator (Vice).")
return
}
println("\nStarting PET emulator...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process=processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
// there's no golden ram.
}
}
@@ -0,0 +1,59 @@
package prog8.code.target.pet
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xb3u // temp storage for a single byte
override val SCRATCH_REG = 0xb4u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x00u..0xffu)
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x00u..0xffu)
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb3u..0xbau) // TODO more?
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if PET can put the virtual regs in ZP")
}
}
@@ -12,18 +12,20 @@ class VirtualMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
override val FLOAT_MEM_SIZE = 8 // 64-bits double
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toFloat().toBits().toUInt()
val hexStr = bits.toString(16).padStart(8, '0')
val bits = num.toDouble().toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
+3 -3
View File
@@ -30,16 +30,16 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -1,6 +1,7 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
@@ -32,7 +33,14 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
fun prefixNamedNode(node: PtNamedNode) {
node.name = "p8_${node.name}"
when(node) {
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
is PtBlock -> node.name = "p8b_${node.name}"
is PtLabel -> node.name = "p8l_${node.name}"
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
else -> node.name = "p8_${node.name}"
}
}
fun prefixSymbols(node: PtNode) {
@@ -47,7 +55,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
is PtFunctionCall -> {
val stNode = st.lookup(node.name)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
@@ -58,25 +66,16 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
is PtJump -> {
if(node.identifier!=null) {
val stNode = st.lookup(node.identifier!!.name)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
else if(node.generatedLabel!=null) {
val stNode = st.lookup(node.generatedLabel!!)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
val stNode = st.lookup(node.identifier!!.name) ?: throw AssemblyError("name not found ${node.identifier}")
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
is PtBlock -> prefixNamedNode(node)
@@ -94,7 +93,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
program.allBlocks().forEach { block ->
if (!block.noSymbolPrefixing) {
if (!block.options.noSymbolPrefixing) {
prefixSymbols(block)
}
}
@@ -124,8 +123,20 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
}
private fun prefixScopedName(name: String, type: Char): String {
if('.' !in name)
return "p8${type}_$name"
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPrefixed = "p8${type}_${parts.last()}"
// the parts in between are assumed to be subroutine scopes.
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
return prefixed.joinToString(".")
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
name = name.split('.').map {"p8_$it" }.joinToString(".")
name = prefixScopedName(name, 'v')
if(value==null)
return this
@@ -137,7 +148,7 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
is PtNumber -> newValue.add(elt)
is PtAddressOf -> {
if(elt.definingBlock()?.noSymbolPrefixing==true)
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
newValue.add(elt)
else {
val newAddr = PtAddressOf(elt.position)
@@ -155,19 +166,14 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
}
private fun PtJump.prefix(parent: PtNode, st: SymbolTable): PtJump {
val jump = if(identifier!=null) {
val prefixedIdent = identifier!!.prefix(this, st)
PtJump(prefixedIdent, address, generatedLabel, position)
} else {
val prefixedLabel = generatedLabel!!.split('.').map {"p8_$it" }.joinToString(".")
PtJump(null, address, prefixedLabel, position)
}
val prefixedIdent = identifier!!.prefix(this, st)
val jump = PtJump(prefixedIdent, address, position)
jump.parent = parent
return jump
}
private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
val newName = name.split('.').map {"p8_$it" }.joinToString(".")
val newName = prefixScopedName(name, 's')
val call = PtFunctionCall(newName, void, type, position)
call.children.addAll(children)
call.children.forEach { it.parent = call }
@@ -176,11 +182,29 @@ private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
}
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
val target = st.lookup(name)
if(target?.astNode?.definingBlock()?.noSymbolPrefixing==true)
var target = st.lookup(name)
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
val newName = name.split('.').map { "p8_$it" }.joinToString(".")
if(target==null) {
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
target = st.lookup(name.dropLast(4))
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
}
}
val prefixType = when(target!!.type) {
StNodeType.BLOCK -> 'b'
StNodeType.SUBROUTINE, StNodeType.ROMSUB -> 's'
StNodeType.LABEL -> 'l'
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
StNodeType.CONSTANT -> 'c'
StNodeType.BUILTINFUNC -> 's'
StNodeType.MEMORYSLAB -> 'v'
else -> '?'
}
val newName = prefixScopedName(name, prefixType)
val node = PtIdentifier(newName, type, position)
node.parent = parent
return node
@@ -296,6 +320,18 @@ class AsmGen6502Internal (
name
}
fun asmVariableName(st: StNode, scope: PtSub?): String {
val name = asmVariableName(st.scopedName)
if(scope==null)
return name
// remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = scope.scopedName
return if (name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length+1)
else
name
}
internal fun getTempVarName(dt: DataType): String {
return when(dt) {
DataType.UBYTE -> "cx16.r9L"
@@ -303,7 +339,7 @@ class AsmGen6502Internal (
DataType.UWORD -> "cx16.r9"
DataType.WORD -> "cx16.r9s"
DataType.FLOAT -> "floats.floats_temp_var"
else -> throw AssemblyError("invalid dt $dt")
else -> throw AssemblyError("invalid dt")
}
}
@@ -487,40 +523,30 @@ class AsmGen6502Internal (
}
}
internal fun loadScaledArrayIndexIntoRegister(
expr: PtArrayIndexer,
elementDt: DataType,
register: CpuRegister
) {
internal fun loadScaledArrayIndexIntoRegister(expr: PtArrayIndexer, register: CpuRegister) {
val reg = register.toString().lowercase()
val indexnum = expr.index.asConstInteger()
if (indexnum != null) {
val indexValue = if(expr.splitWords)
indexnum
else
indexnum * options.compTarget.memorySize(elementDt)
indexnum * options.compTarget.memorySize(expr.type)
out(" ld$reg #$indexValue")
return
}
val indexVar = expr.index as? PtIdentifier
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
val indexName = asmVariableName(indexVar)
if(expr.splitWords) {
when (register) {
CpuRegister.A -> out(" lda $indexName")
CpuRegister.X -> out(" ldx $indexName")
CpuRegister.Y -> out(" ldy $indexName")
}
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
return
}
when (elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
when (expr.type) {
in ByteDatatypes -> {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
}
in WordDatatypes -> {
out(" lda $indexName | asl a")
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out(" asl a")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
@@ -529,12 +555,13 @@ class AsmGen6502Internal (
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out("""
lda $indexName
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc $indexName"""
adc P8ZP_SCRATCH_REG"""
)
when (register) {
CpuRegister.A -> {}
@@ -568,7 +595,7 @@ class AsmGen6502Internal (
when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes)
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes, true)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
@@ -582,6 +609,34 @@ class AsmGen6502Internal (
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
when (target.datatype) {
in ByteDatatypes -> {
if (value.asConstInteger()==0) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${target.asmVarname}")
else
out("stz ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val address = target.memory!!.address.asConstInteger()
if(address!=null) {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${address.toHex()}")
else
out(" stz ${address.toHex()}")
return
}
}
TargetStorageKind.REGISTER -> {
val zero = PtNumber(DataType.UBYTE, 0.0, value.position)
zero.parent = value
assignExpressionToRegister(zero, target.register!!, false)
return
}
else -> { }
}
}
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
}
@@ -661,7 +716,6 @@ class AsmGen6502Internal (
if (jump is PtJump) {
// jump somewhere if X!=0
val label = when {
jump.generatedLabel!=null -> jump.generatedLabel!!
jump.identifier!=null -> asmSymbolName(jump.identifier!!)
jump.address!=null -> jump.address!!.toHex()
else -> throw AssemblyError("weird jump")
@@ -961,7 +1015,6 @@ $repeatLabel""")
private fun getJumpTarget(jump: PtJump): Pair<String, Boolean> {
val ident = jump.identifier
val label = jump.generatedLabel
val addr = jump.address
return when {
ident!=null -> {
@@ -972,7 +1025,6 @@ $repeatLabel""")
else
Pair(asmSymbolName(ident), false)
}
label!=null -> Pair(label, false)
addr!=null -> Pair(addr.toHex(), false)
else -> Pair("????", false)
}
@@ -1026,6 +1078,15 @@ $repeatLabel""")
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out(label)
if(options.breakpointCpuInstruction) {
val instruction =
when(options.compTarget.machine.cpu) {
CpuType.CPU6502 -> "brk"
CpuType.CPU65c02 -> "stp"
else -> throw AssemblyError("invalid cpu type")
}
out(" $instruction")
}
}
internal fun signExtendAYlsb(valueDt: DataType) {
@@ -1213,7 +1274,6 @@ $repeatLabel""")
val rightConstVal = right as? PtNumber
val label = when {
jump.generatedLabel!=null -> jump.generatedLabel!!
jump.identifier!=null -> asmSymbolName(jump.identifier!!)
jump.address!=null -> jump.address!!.toHex()
else -> throw AssemblyError("weird jump")
@@ -2417,8 +2477,7 @@ $repeatLabel""")
cmp #<$number
bne $jumpIfFalseLabel
cpy #>$number
bne $jumpIfFalseLabel
""")
bne $jumpIfFalseLabel""")
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -2426,18 +2485,21 @@ $repeatLabel""")
cmp ${asmVariableName(right)}
bne $jumpIfFalseLabel
cpy ${asmVariableName(right)}+1
bne $jumpIfFalseLabel
""")
bne $jumpIfFalseLabel""")
}
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel
""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
// assignmentAsmGen.assignAddressOf(target, name, right.arrayIndexExpr)
} else {
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel""")
}
}
else -> {
if(left.isSimple()) {
@@ -2518,12 +2580,16 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
}
else -> {
if(left.isSimple()) {
@@ -2849,8 +2915,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
val name = asmSymbolName(left.identifier)
code("#>$name", "#<$name")
return true
if(left.isFromArrayElement) {
TODO("address-of array element $name at ${left.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -2898,8 +2968,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
code("#>$name", "#<$name")
return true
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -2983,30 +3057,16 @@ $repeatLabel""")
}
}
internal fun popCpuStack(dt: DataType, target: IPtVariable, scope: IPtSubroutine?) {
// note: because A is pushed first so popped last, saving A is often not required here.
val targetAsmSub = (target as PtNode).definingAsmSub()
if(targetAsmSub != null) {
val parameter = targetAsmSub.parameters.first { it.second.name==target.name }
popCpuStack(targetAsmSub, parameter.second, parameter.first)
return
}
val scopedName = when(target) {
is PtConstant -> target.scopedName
is PtMemMapped -> target.scopedName
is PtVariable -> target.scopedName
else -> throw AssemblyError("weird target var")
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, this, target.type, scope, target.position, variableAsmName = asmVariableName(scopedName))
internal fun popCpuStack(dt: DataType) {
if (dt in ByteDatatypes) {
out(" pla")
assignRegister(RegisterOrPair.A, tgt)
} else {
} else if (dt in WordDatatypes) {
if (isTargetCpu(CpuType.CPU65c02))
out(" ply | pla")
else
out(" pla | tay | pla")
assignRegister(RegisterOrPair.AY, tgt)
} else {
throw AssemblyError("can't pop type $dt")
}
}
@@ -3031,7 +3091,11 @@ $repeatLabel""")
}
internal fun popFAC1() {
out(" jsr floats.popFAC1")
out(" clc | jsr floats.popFAC")
}
internal fun popFAC2() {
out(" sec | jsr floats.popFAC")
}
internal fun needAsaveForExpr(arg: PtExpression): Boolean =
@@ -3054,9 +3118,20 @@ $repeatLabel""")
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "prog8_label_asm_${generatedLabelSequenceNumber}_$postfix"
return "label_asm_${generatedLabelSequenceNumber}_$postfix"
}
fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""
pha
lda #<$floatConst
sta P8ZP_SCRATCH_W1
lda #>$floatConst
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
}
/**
@@ -43,6 +43,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeUnneededTempvarInAdd(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
@@ -374,15 +381,9 @@ private fun optimizeStoreLoadSame(
} else if(first=="phx" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txa"))
} else if(first=="phx" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txy"))
} else if(first=="phy" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tya"))
} else if(first=="phy" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tyx"))
}
}
return mods
@@ -508,4 +509,27 @@ private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<Stri
optimize('y', lines)
}
return mods
}
}
private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sequence: sta P8ZP_SCRATCH_XX / lda something / clc / adc P8ZP_SCRATCH_XX
// this can be performed without the scratch variable: clc / adc something
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
if(first.startsWith("sta P8ZP_SCRATCH_") && second.startsWith("lda") && third.startsWith("clc") && fourth.startsWith("adc P8ZP_SCRATCH_") ) {
if(fourth.substring(4)==first.substring(4)) {
mods.add(Modification(lines[0].index, false, " clc"))
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
}
return mods
}
@@ -1,9 +1,8 @@
package prog8.codegen.cpu6502
import prog8.code.ast.*
import prog8.code.ast.PtAsmSub
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
@@ -11,50 +10,19 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (X last), except A,
// 3) single CPU registers (order Y,X,A),
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.first.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
fun asmsub6502ArgsHaveRegisterClobberRisk(
args: List<PtExpression>,
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
): Boolean {
fun isClobberRisk(expr: PtExpression): Boolean {
when (expr) {
is PtArrayIndexer -> {
return params.any {
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is PtBuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple()
}
else -> return !expr.isSimple()
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}
@@ -22,15 +22,20 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
when (compTarget.name) {
in setOf("c64", "c128", "cx16") -> {
in setOf("c64", "c128", "cx16", "pet32") -> {
// CBM machines .prg generation.
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"-Wall", // "-Wno-strict-bool", "-Werror",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
@@ -59,10 +64,15 @@ internal class AssemblyProgram(
// TODO are these options okay for atari?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"-Wall", // "-Werror", "-Wno-strict-bool"
"--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
@@ -1,5 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.StMemVar
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
@@ -41,43 +42,64 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokemon" -> { /* meme function */ }
"pokef" -> funcPokeF(fcall)
"pokemon" -> {
val memread = PtMemoryByte(fcall.position)
memread.add(fcall.args[0])
memread.parent = fcall
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
asmgen.out(" pha")
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
asmgen.assignExpressionTo(fcall.args[1], memtarget)
asmgen.out(" pla")
}
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
"pop" -> {
require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
}
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UBYTE, target, fcall.definingISub())
}
"popw" -> {
require(fcall.args[0] is PtIdentifier) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
}
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
val target = symbol!!.astNode as IPtVariable
asmgen.popCpuStack(DataType.UWORD, target, fcall.definingISub())
}
"rsave" -> funcRsave()
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"prog8_lib_stringcompare" -> funcStringCompare(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {
DataType.UBYTE -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
asmgen.out(" tay | jsr math.multiply_bytes")
if(resultRegister!=null) {
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
}
}
DataType.UWORD -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr math.square")
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
else -> {
throw AssemblyError("optimized square only for integer types")
}
}
}
private fun funcDivmod(fcall: PtBuiltinFunctionCall) {
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
@@ -89,8 +111,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[3].position, var3name)
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false)
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false)
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false, false)
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false, false)
}
private fun funcDivmodW(fcall: PtBuiltinFunctionCall) {
@@ -110,9 +132,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
sty $remainderVar+1""")
}
private fun funcStringCompare(fcall: PtBuiltinFunctionCall) {
private fun funcStringCompare(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignWordOperandsToAYAndVar(fcall.args[0], fcall.args[1], "P8ZP_SCRATCH_W2")
asmgen.out(" jsr prog8_lib.strcmp_mem")
if(resultRegister!=null) {
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
}
}
private fun funcRsave() {
@@ -152,7 +177,22 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
plp""")
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
private fun funcCall(fcall: PtBuiltinFunctionCall) {
val constAddr = fcall.args[0].asConstInteger()
if(constAddr!=null) {
asmgen.out(" jsr ${constAddr.toHex()}")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address
asmgen.out("""
sta (+)+1
sty (+)+2
+ jsr 0 ; modified""")
}
// note: the routine can return a word value (in AY)
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
@@ -162,10 +202,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.jsrfar
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
private fun funcCmp(fcall: PtBuiltinFunctionCall) {
@@ -252,11 +295,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when(fcall.args[0].type) {
DataType.UBYTE -> {
asmgen.out(" ldy #0 | jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
DataType.UWORD -> {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
@@ -267,106 +310,112 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single()
if (variable is PtIdentifier) {
val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("cannot reverse a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
val varName = asmgen.asmVariableName(variable)
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
val varName = asmgen.asmVariableName(variable)
when (decl.type) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_w""")
}
DataType.STR -> {
val stringLength = (symbol as StStaticVariable).length!!-1
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$stringLength
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr floats.func_reverse_f""")
}
in SplitWordArrayTypes -> TODO("split word reverse")
else -> throw AssemblyError("weird type")
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_w""")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr floats.func_reverse_f""")
}
in SplitWordArrayTypes -> {
// reverse the lsb and msb arrays both, independently
asmgen.out("""
lda #<${varName}_lsb
ldy #>${varName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b
lda #<${varName}_msb
ldy #>${varName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
else -> throw AssemblyError("weird type")
}
}
private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single()
if (variable is PtIdentifier) {
val symbol = asmgen.symbolTable.lookup(variable.name)
val decl = symbol!!.astNode as IPtVariable
val varName = asmgen.asmVariableName(variable)
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("cannot sort a constant")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val varName = asmgen.asmVariableName(variable)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
when (decl.type) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (decl.type == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (decl.type == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
DataType.STR -> {
val stringLength = (symbol as StStaticVariable).length!!-1
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$stringLength
jsr prog8_lib.func_sort_ub""")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
in SplitWordArrayTypes -> TODO("split word sort")
else -> throw AssemblyError("weird type")
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
} else
throw AssemblyError("weird type")
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_sort_ub""")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
in SplitWordArrayTypes -> TODO("split words sort")
else -> throw AssemblyError("weird type")
}
}
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
@@ -375,8 +424,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror2", 'b')
asmgen.out(" jsr prog8_lib.ror2_array_ub")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #\$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
@@ -397,10 +447,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.splitWords)
TODO("ror2 split words ${what.position}")
translateRolRorArrayArgs(what.variable, what, "ror2", 'w')
asmgen.out(" jsr prog8_lib.ror2_array_uw")
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #\$80 | sta ${varname}_msb,x |+ ")
else
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #\$80 | sta ${varname}+1,x |+ ")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
@@ -419,8 +471,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "ror", 'b')
asmgen.out(" jsr prog8_lib.ror_array_ub")
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" ror ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
@@ -429,15 +484,19 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.out(" php")
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.restoreRegisterStack(CpuRegister.X, false)
asmgen.out("""
plp
+ ror ${'$'}ffff,x ; modified""")
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
if(!what.address.isSimple()) asmgen.out(" plp")
asmgen.out("""
sta (+) + 1
sty (+) + 2
@@ -455,10 +514,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.splitWords)
TODO("ror split words ${what.position}")
translateRolRorArrayArgs(what.variable, what, "ror", 'w')
asmgen.out(" jsr prog8_lib.ror_array_uw")
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
else
asmgen.out(" ror ${varname}+1,x | ror ${varname},x")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
@@ -477,8 +540,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol2", 'b')
asmgen.out(" jsr prog8_lib.rol2_array_ub")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" lda ${varname},x | cmp #\$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
@@ -499,10 +563,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.splitWords)
TODO("rol2 split words ${what.position}")
translateRolRorArrayArgs(what.variable, what, "rol2", 'w')
asmgen.out(" jsr prog8_lib.rol2_array_uw")
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb |+")
else
asmgen.out(" asl ${varname},x | rol ${varname}+1,x | bcc + | inc ${varname},x |+ ")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
@@ -521,8 +587,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
translateRolRorArrayArgs(what.variable, what, "rol", 'b')
asmgen.out(" jsr prog8_lib.rol_array_ub")
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
asmgen.out(" rol ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
@@ -531,15 +600,19 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.out(" php")
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.restoreRegisterStack(CpuRegister.X, false)
asmgen.out("""
plp
+ rol ${'$'}ffff,x ; modified""")
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
if(!what.address.isSimple()) asmgen.out(" plp")
asmgen.out("""
sta (+) + 1
sty (+) + 2
@@ -557,10 +630,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.splitWords)
TODO("rol split words ${what.position}")
translateRolRorArrayArgs(what.variable, what, "rol", 'w')
asmgen.out(" jsr prog8_lib.rol_array_uw")
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
else
asmgen.out(" rol ${varname},x | rol ${varname}+1,x")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
@@ -573,21 +650,83 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun translateRolRorArrayArgs(arrayvar: PtIdentifier, indexer: PtArrayIndexer, operation: String, dt: Char) {
if(indexer.splitWords)
TODO("rol/ror split words access ${indexer.position}")
if(arrayvar.type==DataType.UWORD) {
if(dt!='b')
throw AssemblyError("non-array var indexing requires bytes dt")
asmgen.assignExpressionToVariable(arrayvar, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD)
private fun funcSetLsbMsb(fcall: PtBuiltinFunctionCall, msb: Boolean) {
val target: AsmAssignTarget
when(fcall.args[0]) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
}
is PtNumber -> {
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
val mem = PtMemoryByte(fcall.position)
mem.add(PtNumber(DataType.UBYTE, num, fcall.position))
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtAddressOf -> {
val mem = PtMemoryByte(fcall.position)
if(msb) {
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
address.add(fcall.args[0])
address.add(PtNumber(address.type, 1.0, fcall.args[0].position))
mem.add(address)
} else {
mem.add(fcall.args[0])
}
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
require(!indexer.usesPointerVariable)
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.ARRAY_UB, arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
msbAdd = 0
} else {
elementSize = 2
msbAdd = if(msb) 1 else 0
}
// double the index because of word array (if not split), add one if msb (if not split)
val constIndexNum = (indexer.index as? PtNumber)?.number
if(constIndexNum!=null) {
indexer.children[1] = PtNumber(indexer.index.type, constIndexNum*elementSize + msbAdd, indexer.position)
indexer.children[1].parent = indexer
} else {
val multipliedIndex: PtExpression
if(elementSize==1) {
multipliedIndex = indexer.index
} else {
multipliedIndex = PtBinaryExpression("<<", indexer.index.type, indexer.position)
multipliedIndex.add(indexer.index)
multipliedIndex.add(PtNumber(DataType.UBYTE, 1.0, indexer.position))
}
if(msbAdd>0) {
val msbIndex = PtBinaryExpression("+", indexer.index.type, indexer.position)
msbIndex.add(multipliedIndex)
msbIndex.add(PtNumber(DataType.UBYTE, msbAdd.toDouble(), indexer.position))
indexer.children[1] = msbIndex
msbIndex.parent = indexer
} else {
indexer.children[1] = multipliedIndex
multipliedIndex.parent=indexer
}
}
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
}
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
}
if(fcall.args[1].asConstInteger() == 0) {
assignAsmGen.assignConstantByte(target, 0)
} else {
val p = arrayvar.parent
val addressOf = PtAddressOf(arrayvar.position)
addressOf.add(arrayvar)
addressOf.parent = p
asmgen.assignExpressionToVariable(addressOf, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE)
}
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
@@ -601,20 +740,64 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
}
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().type
val array = fcall.args[0] as PtIdentifier
when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
in SplitWordArrayTypes -> TODO("split word any/all")
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A")
}
DataType.ARRAY_F -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr floats.func_${fcall.name}_f_into_A")
}
in SplitWordArrayTypes -> {
val numElements = (asmgen.symbolTable.lookup(array.name) as StStaticVariable).length
when(fcall.name) {
"any" -> {
// any(lsb-array) or any(msb-array)
val arrayName = asmgen.asmVariableName(array)
asmgen.out("""
lda #<${arrayName}_lsb
ldy #>${arrayName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out(" bne +") // shortcircuit
asmgen.out("""
pha
lda #<${arrayName}_msb
ldy #>${arrayName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out("""
sta P8ZP_SCRATCH_REG
pla
ora P8ZP_SCRATCH_REG
+""")
}
"all" -> {
TODO("split words all")
}
else -> throw AssemblyError("weird call")
}
}
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
}
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
@@ -623,7 +806,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (dt) {
DataType.BYTE -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false, true)
}
DataType.WORD -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
@@ -643,6 +826,41 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPokeF(fcall: PtBuiltinFunctionCall) {
when(val number = fcall.args[1]) {
is PtIdentifier -> {
val varName = asmgen.asmVariableName(number)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
pha
lda #<$varName
sta P8ZP_SCRATCH_W1
lda #>$varName
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.assignExpressionTo(fcall.args[1],
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, tempvar, null, null, null, null))
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
pha
lda #<$tempvar
sta P8ZP_SCRATCH_W1
lda #>$tempvar
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
}
}
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
@@ -699,6 +917,15 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr floats.MOVFM")
if(resultRegister!=null) {
assignAsmGen.assignFAC1float(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, null, null, null, resultRegister, null))
}
}
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
fun fallback() {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
@@ -723,11 +950,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out("""
ldy #0
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
}
} else fallback()
}
@@ -740,11 +967,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.out("""
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
} else fallback()
}
else -> fallback()
@@ -771,7 +998,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed)
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed, true)
}
in WordDatatypes -> {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
@@ -787,55 +1014,59 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMin(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type in SignedDatatypes
if(fcall.type in ByteDatatypes) {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
asmgen.out("""
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
} else if(fcall.type in WordDatatypes) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
when (fcall.type) {
in ByteDatatypes -> {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bpl +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jmp ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
+""")
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
}
in WordDatatypes -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bpl +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jmp ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
+""")
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
else -> {
throw AssemblyError("min float not supported")
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
} else {
throw AssemblyError("min float not supported")
}
}
@@ -967,17 +1198,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
}
RegisterOrPair.X -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" pla")
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" pla")
}
else -> throw AssemblyError("invalid reg")
@@ -1011,37 +1242,55 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
when(resultRegister) {
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the lsb byte out of the word array
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" lda $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" ldx $arrayVar,y")
}
else -> throw AssemblyError("invalid reg")
}
} else when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.X -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tay | cpy #0")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args.single(), resultRegister)
asmgen.assignExpressionToRegister(arg, resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
@@ -1052,16 +1301,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun outputAddressAndLenghtOfArray(arg: PtExpression) {
// address in P8ZP_SCRATCH_W1, number of elements in A
arg as PtIdentifier
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
// address goes in P8ZP_SCRATCH_W1, number of elements in A
val symbol = asmgen.symbolTable.lookup(arg.name)
val arrayVar = symbol!!.astNode as IPtVariable
val numElements = when(arrayVar) {
is PtConstant -> null
is PtMemMapped -> arrayVar.arraySize
is PtVariable -> arrayVar.arraySize
} ?: throw AssemblyError("length of non-array requested")
val numElements = when(symbol) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
}
val identifierName = asmgen.asmVariableName(arg)
asmgen.out("""
lda #<$identifierName
@@ -1,7 +1,11 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.code.ast.*
import prog8.code.StMemVar
import prog8.code.StStaticVariable
import prog8.code.ast.PtForLoop
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtRange
import prog8.code.core.*
import kotlin.math.absoluteValue
@@ -255,11 +259,11 @@ $loopLabel""")
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
tax
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
txa
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
$modifiedLabel sbc #0 ; modified
@@ -332,11 +336,10 @@ $endLabel""")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val symbol = asmgen.symbolTable.lookup(ident.name)
val decl = symbol!!.astNode as IPtVariable
val numElements = when(decl) {
is PtConstant -> throw AssemblyError("length of non-array requested")
is PtMemMapped -> decl.arraySize
is PtVariable -> decl.arraySize
val numElements = when(symbol) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
}
when(iterableDt) {
DataType.STR -> {
@@ -364,7 +367,7 @@ $loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements!!<=255u) {
if(numElements<=255) {
asmgen.out("""
ldy $indexVar
iny
@@ -379,7 +382,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(numElements>=16u) {
if(numElements>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
@@ -392,7 +395,7 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
val length = numElements!! * 2u
val length = numElements * 2
val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
@@ -403,7 +406,7 @@ $loopLabel sty $indexVar
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127u) {
if(length<=127) {
asmgen.out("""
ldy $indexVar
iny
@@ -420,7 +423,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16u) {
if(length>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
@@ -433,7 +436,6 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> {
numElements!!
val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
@@ -444,7 +446,7 @@ $loopLabel sty $indexVar
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255u) {
if(numElements<=255) {
asmgen.out("""
ldy $indexVar
iny
@@ -459,7 +461,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(numElements>=16u) {
if(numElements>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
@@ -26,7 +26,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol!!.astNode as IPtSubroutine
val sub = symbol?.astNode as IPtSubroutine
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub is PtAsmSub) {
@@ -69,37 +69,59 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name == "lsb" || arg.name == "msb")
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
return !arg.isSimple()
}
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtMemoryByte -> false
is PtNumber -> false
else -> true
}
}
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
val registersUsed = mutableListOf<RegisterOrStatusflag>();
if(callee.parameters.isEmpty())
return
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
}
argOrder.forEach {
val param = callee.parameters[it]
asmgen.popCpuStack(callee, param.second, param.first)
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
optimalEvalOrder.forEach {
val param = sub.parameters[it]
val arg = call.args[it]
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
else if(registersUsed.any {it.statusflag!=null}) {
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
used
}
} else {
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
}
}
}
@@ -112,7 +134,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.assignExpressionToVariable(value, varName, parameter.type)
}
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
// pass argument via a register parameter
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -141,6 +163,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value)
// note: cannot use X register here to store A because it might be used for other arguments
asmgen.out("""
pha
clc
@@ -155,6 +178,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
return RegisterOrStatusflag(null, statusflag)
}
else {
// via register or register pair
@@ -187,6 +211,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
}
return RegisterOrStatusflag(register, null)
}
}
@@ -208,3 +233,5 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
return false
}
}
@@ -75,7 +75,7 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
dec ${asmArrayvarname}_msb+$constIndex
+ dec ${asmArrayvarname}_lsb+$constIndex""")
} else {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.X)
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
if(incr)
asmgen.out(" inc ${asmArrayvarname}_lsb,x | bne + | inc ${asmArrayvarname}_msb,x |+")
else
@@ -127,7 +127,7 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.X)
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
when(elementDt) {
in ByteDatatypes -> {
if(targetArrayIdx.usesPointerVariable) {
@@ -34,8 +34,8 @@ internal class ProgramAndVarsGen(
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "p8_main" && allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main' or 'p8_main'")
if(allBlocks.first().name != "p8b_main" && allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main' or 'p8b_main'")
if(errors.noErrors()) {
program.allBlocks().forEach { block2asm(it) }
@@ -133,29 +133,22 @@ internal class ProgramAndVarsGen(
pha""")
}
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
when(compTarget.name) {
"cx16" -> {
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr p8_main.p8_start")
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp sys.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr p8_main.p8_start | lda #31 | sta $01")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
else
asmgen.out(" rts")
asmgen.out(" jsr p8b_main.p8s_start | lda #31 | sta $01")
asmgen.out(" jmp sys.cleanup_at_exit")
}
"c128" -> {
asmgen.out(" jsr p8_main.p8_start | lda #0 | sta ${"$"}ff00")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
else
asmgen.out(" rts")
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
asmgen.out(" jmp sys.cleanup_at_exit")
}
else -> asmgen.jmp("p8_main.p8_start")
else -> asmgen.jmp("p8b_main.p8s_start")
}
}
@@ -173,47 +166,98 @@ internal class ProgramAndVarsGen(
}
private fun footer() {
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
var relocatedBssEnd = 0u
if(options.varsGolden) {
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssVars = true
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
}
else if(options.varsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssVars = true
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
}
if(options.slabsGolden) {
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssSlabs = true
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
}
else if(options.slabsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssSlabs = true
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
}
asmgen.out("; bss sections")
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(options.varsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
}
// BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
if(symboltable.allMemorySlabs.isNotEmpty()) {
if(relocateBssVars) {
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out(" .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
if(relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
// BSS vars followed by memory() slabs, concatenated at the end of the program.
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(symboltable.allMemorySlabs.isNotEmpty()) {
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
}
asmgen.out("prog8_program_end\t; end of program label for progend()")
if(relocateBssSlabs) {
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
}
}
}
private fun block2asm(block: PtBlock) {
asmgen.out("")
asmgen.out("; ---- block: '${block.name}' ----")
if(block.address!=null)
asmgen.out("* = ${block.address!!.toHex()}")
if(block.options.address!=null)
asmgen.out("* = ${block.options.address!!.toHex()}")
else {
if(block.alignment==PtBlock.BlockAlignment.WORD)
if(block.options.alignment==PtBlock.BlockAlignment.WORD)
asmgen.out("\t.align 2")
else if(block.alignment==PtBlock.BlockAlignment.PAGE)
else if(block.options.alignment==PtBlock.BlockAlignment.PAGE)
asmgen.out("\t.align $100")
}
asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc"))
asmgen.outputSourceLine(block)
createBlockVariables(block)
@@ -236,7 +280,7 @@ internal class ProgramAndVarsGen(
asmgen.out(" rts\n .bend")
}
asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
asmgen.out(if(block.options.forceOutput) "\n\t.bend" else "\n\t.pend")
}
private fun getVars(scope: StNode): Map<String, StNode> =
@@ -276,7 +320,7 @@ internal class ProgramAndVarsGen(
val asmStartScope: String
val asmEndScope: String
if(sub.definingBlock()!!.forceOutput) {
if(sub.definingBlock()!!.options.forceOutput) {
asmStartScope = ".block"
asmEndScope = ".bend"
} else {
@@ -299,7 +343,7 @@ internal class ProgramAndVarsGen(
val asmStartScope: String
val asmEndScope: String
if(sub.definingBlock()!!.forceOutput) {
if(sub.definingBlock()!!.options.forceOutput) {
asmStartScope = ".block"
asmEndScope = ".bend"
} else {
@@ -331,7 +375,7 @@ internal class ProgramAndVarsGen(
asmsubs2asm(sub.children)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if((sub.name=="start" || sub.name=="p8_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8_main"))
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
@@ -428,12 +472,12 @@ internal class ProgramAndVarsGen(
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0L
sty cx16.r0H
sta cx16.r0
sty cx16.r0+1
lda #<${name}
ldy #>${name}
sta cx16.r1L
sty cx16.r1H
sta cx16.r1
sty cx16.r1+1
lda #<$size
ldy #>$size
jsr sys.memcopy""")
@@ -452,7 +496,6 @@ internal class ProgramAndVarsGen(
}
asmgen.out("""+
ldx #127 ; init estack ptr (half page)
clv
clc""")
}
@@ -498,7 +541,15 @@ internal class ProgramAndVarsGen(
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
if(variable.dt in SplitWordArrayTypes) {
val lsbAddr = zpvar.address
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
} else {
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
}
}
@@ -1,7 +1,10 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtExpression
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.AsmGen6502Internal
//
@@ -20,13 +23,13 @@ internal class AnyExprAsmGen(
return assignByteBinExpr(expr, assign)
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
require(expr.operator in ComparisonOperators)
TODO("words operands comparison -> byte")
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
}
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
require(expr.operator in ComparisonOperators)
return assignFloatBinExpr(expr, assign)
}
TODO("weird expr operand types")
throw AssemblyError("weird expr operand types: ${expr.left.type} and ${expr.right.type}")
}
in WordDatatypes -> {
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
@@ -46,54 +49,24 @@ internal class AnyExprAsmGen(
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
when(expr.operator) {
"+" -> {
TODO("word + at ${expr.position}")
}
"-" -> {
TODO("word - at ${expr.position}")
}
"*" -> {
TODO("word * at ${expr.position}")
}
"/" -> {
TODO("word / at ${expr.position}")
}
"<<" -> {
TODO("word << at ${expr.position}")
}
">>" -> {
TODO("word >> at ${expr.position}")
}
"%" -> {
TODO("word % at ${expr.position}")
}
"&", "and" -> {
TODO("word and at ${expr.position}")
}
"|", "or" -> {
TODO("word or at ${expr.position}")
}
"^", "xor" -> {
TODO("word xor at ${expr.position}")
}
"==" -> {
TODO("word == at ${expr.position}")
}
"!=" -> {
TODO("word != at ${expr.position}")
}
"<" -> {
TODO("word < at ${expr.position}")
}
"<=" -> {
TODO("word <= at ${expr.position}")
}
">" -> {
TODO("word > at ${expr.position}")
}
">=" -> {
TODO("word >= at ${expr.position}")
}
"+" -> TODO("word + at ${expr.position}")
"-" -> TODO("word - at ${expr.position}")
"*" -> TODO("word * at ${expr.position}")
"/" -> TODO("word / at ${expr.position}")
"<<" -> TODO("word << at ${expr.position}")
">>" -> TODO("word >> at ${expr.position}")
"%" -> TODO("word % at ${expr.position}")
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
"&" -> TODO("word and at ${expr.position}")
"|" -> TODO("word or at ${expr.position}")
"^", "xor" -> TODO("word xor at ${expr.position}")
"==" -> TODO("word == at ${expr.position}")
"!=" -> TODO("word != at ${expr.position}")
"<" -> TODO("word < at ${expr.position}")
"<=" -> TODO("word <= at ${expr.position}")
">" -> TODO("word > at ${expr.position}")
">=" -> TODO("word >= at ${expr.position}")
else -> return false
}
}
@@ -131,7 +104,9 @@ internal class AnyExprAsmGen(
"%" -> {
TODO("byte % at ${expr.position}")
}
"&", "and" -> {
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
@@ -139,7 +114,7 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"|", "or" -> {
"|" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
@@ -180,37 +155,25 @@ internal class AnyExprAsmGen(
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FADDT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"-" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FSUBT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"*" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FMULTT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"/" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FDIVT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
@@ -255,6 +218,31 @@ internal class AnyExprAsmGen(
}
}
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
// this requires some more forced copying around of float values in certain cases
if (right.isSimple()) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
}
Cx16Target.NAME -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
if (!right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
if (!right.isSimple()) asmgen.popFAC1()
}
else -> TODO("don't know how to evaluate float expression for selected compilation target")
}
}
private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+13 -3
View File
@@ -18,6 +18,8 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -27,11 +29,11 @@ internal object DummyStringEncoder : IStringEncoding {
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
val infos = mutableListOf<String>()
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
@@ -45,6 +47,12 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
infos.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
@@ -52,10 +60,11 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
clear()
}
@@ -64,5 +73,6 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
fun clear() {
errors.clear()
warnings.clear()
infos.clear()
}
}
+3 -2
View File
@@ -22,6 +22,7 @@ class TestCodegen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
@@ -44,7 +45,7 @@ class TestCodegen: FunSpec({
//}
val codegen = AsmGen6502(prefixSymbols = false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -94,7 +95,7 @@ class TestCodegen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
+2 -2
View File
@@ -36,10 +36,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+3 -3
View File
@@ -30,16 +30,16 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -50,25 +50,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
assignment: PtAugmentedAssign
): IRCodeChunks {
val value = assignment.value
val vmDt = irType(value.type)
val targetDt = irType(assignment.target.type)
val signed = assignment.target.type in SignedDatatypes
return when(assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
"-=" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
"/=" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
"|=" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
"&=" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
"^=" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
"%=" -> expressionEval.operatorModuloInplace(address, null, vmDt, value)
"==" -> expressionEval.operatorEqualsInplace(address, null, vmDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, vmDt, value)
"<" -> expressionEval.operatorLessInplace(address, null, vmDt, value.type in SignedDatatypes, value)
">" -> expressionEval.operatorGreaterInplace(address, null, vmDt, value.type in SignedDatatypes, value)
"<=" -> expressionEval.operatorLessEqualInplace(address, null, vmDt, value.type in SignedDatatypes, value)
">=" -> expressionEval.operatorGreaterEqualInplace(address, null, vmDt, value.type in SignedDatatypes, value)
in PrefixOperators -> inplacePrefix(assignment.operator, vmDt, address, null)
"+=" -> expressionEval.operatorPlusInplace(address, null, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(address, null, targetDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(address, null, targetDt, value)
"/=" -> expressionEval.operatorDivideInplace(address, null, targetDt, signed, value)
"|=" -> expressionEval.operatorOrInplace(address, null, targetDt, value)
"&=" -> expressionEval.operatorAndInplace(address, null, targetDt, value)
"^=" -> expressionEval.operatorXorInplace(address, null, targetDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(address, null, targetDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(address, null, targetDt, signed, value)
"%=" -> expressionEval.operatorModuloInplace(address, null, targetDt, value)
"==" -> expressionEval.operatorEqualsInplace(address, null, targetDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, targetDt, value)
"<" -> expressionEval.operatorLessInplace(address, null, targetDt, signed, value)
">" -> expressionEval.operatorGreaterInplace(address, null, targetDt, signed, value)
"<=" -> expressionEval.operatorLessEqualInplace(address, null, targetDt, signed, value)
">=" -> expressionEval.operatorGreaterEqualInplace(address, null, targetDt, signed, value)
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, address, null)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
@@ -76,24 +77,27 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
val value = assignment.value
val signed = assignment.target.type in SignedDatatypes
val targetDt = irType(assignment.target.type)
return when (assignment.operator) {
return when(assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value)
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, targetDt, value)
"/=" -> expressionEval.operatorDivideInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
"/=" -> expressionEval.operatorDivideInplace(null, symbol, targetDt, signed, value)
"|=" -> expressionEval.operatorOrInplace(null, symbol, targetDt, value)
"or=" -> expressionEval.operatorLogicalOrInplace(null, symbol, targetDt, value)
"&=" -> expressionEval.operatorAndInplace(null, symbol, targetDt, value)
"^=" -> expressionEval.operatorXorInplace(null, symbol, targetDt, value)
"and=" -> expressionEval.operatorLogicalAndInplace(null, symbol, targetDt, value)
"^=", "xor=" -> expressionEval.operatorXorInplace(null, symbol, targetDt, value)
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, targetDt, value)
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, targetDt, signed, value)
"%=" -> expressionEval.operatorModuloInplace(null, symbol, targetDt, value)
"==" -> expressionEval.operatorEqualsInplace(null, symbol, targetDt, value)
"!=" -> expressionEval.operatorNotEqualsInplace(null, symbol, targetDt, value)
"<" -> expressionEval.operatorLessInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
">" -> expressionEval.operatorGreaterInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
"<=" -> expressionEval.operatorLessEqualInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
">=" -> expressionEval.operatorGreaterEqualInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
"<" -> expressionEval.operatorLessInplace(null, symbol, targetDt, signed, value)
">" -> expressionEval.operatorGreaterInplace(null, symbol, targetDt, signed, value)
"<=" -> expressionEval.operatorLessEqualInplace(null, symbol, targetDt, signed, value)
">=" -> expressionEval.operatorGreaterEqualInplace(null, symbol, targetDt, signed, value)
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, null, symbol)
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
}
@@ -102,11 +106,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
value = PtPrefix(origAssign.operator, origAssign.target.type, origAssign.value.position)
value.add(origAssign.value)
} else {
require(origAssign.operator.endsWith('='))
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.target.type, origAssign.value.position)
val left: PtExpression = origAssign.target.children.single() as PtExpression
value.add(left)
value.add(origAssign.value)
@@ -227,11 +231,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(fixedIndex!=null) {
val chunk = IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
else
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+${fixedIndex*itemsize}")
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
}
result += chunk
} else {
@@ -251,7 +255,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset")
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = variable, symbolOffset = offset)
}
result += chunk
} else {
@@ -266,12 +270,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val chunk = IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
val msbReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
else
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+${fixedIndex*itemsize}")
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
}
result += chunk
} else {
@@ -20,37 +20,68 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"pop" -> funcPop(call)
"popw" -> funcPopw(call)
"push" -> funcPush(call)
"pushw" -> funcPushw(call)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"call" -> funcCall(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call)
"peekw" -> funcPeekW(call)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
"mkword" -> funcMkword(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"rol" -> funcRolRor(Opcode.ROXL, call)
"ror" -> funcRolRor(Opcode.ROXR, call)
"rol2" -> funcRolRor(Opcode.ROL, call)
"ror2" -> funcRolRor(Opcode.ROR, call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
"ror" -> funcRolRor(call)
"rol2" -> funcRolRor(call)
"ror2" -> funcRolRor(call)
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
return if(resultType==IRDataType.FLOAT) {
val resultFpReg = codeGen.registers.nextFreeFloat()
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = resultFpReg, fpReg2 = valueTr.resultFpReg), null)
ExpressionCodeResult(result, resultType, -1, resultFpReg)
}
else {
val resultReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
ExpressionCodeResult(result, resultType, resultReg, -1)
}
}
private fun funcCall(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
if(call.void)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
return ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
@@ -92,6 +123,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@@ -116,6 +148,31 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val result = mutableListOf<IRCodeChunkBase>()
val lengthReg = codeGen.registers.nextFree()
if(arrayName.type in SplitWordArrayTypes) {
// any(lsb-array) or any(msb-array)
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trLsb.resultReg)
val shortcircuitLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = trLsb.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcircuitLabel)
it += IRInstruction(Opcode.PREPARECALL, immediate = 2)
}
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trMsb.resultReg)
addInstr(result, IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=trLsb.resultReg, reg2=trMsb.resultReg), null)
result += IRCodeChunk(shortcircuitLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, trLsb.resultReg, -1)
}
val syscall =
when (arrayName.type) {
DataType.ARRAY_UB,
@@ -125,10 +182,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
@@ -137,6 +193,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
if(arrayName.type in SplitWordArrayTypes) {
// this is a bit complicated to calculate.... have to check all recombined (lsb,msb) words for $0000
TODO("all(split words $arrayName)")
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB,
@@ -147,7 +209,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
@@ -197,14 +260,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val vmDt = irType(call.type)
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, vmDt, reg1 = resultReg, reg2 = tr.resultReg)
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = tr.resultFpReg)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, reg2 = tr.resultReg)
}
}
return ExpressionCodeResult(result, vmDt, resultReg, -1)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcSqrt(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -236,63 +306,40 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
else -> throw AssemblyError("invalid dt for sqrt")
else -> throw AssemblyError("invalid dt")
}
}
private fun funcPop(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return ExpressionCodeResult(result, IRDataType.BYTE, reg, -1)
}
private fun funcPopw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return ExpressionCodeResult(result, IRDataType.WORD, reg, -1)
}
private fun funcPush(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPushw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val lengthReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
if(arrayName.type in SplitWordArrayTypes) {
// reverse the lsb and msb arrays both, independently
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
in SplitWordArrayTypes -> TODO("split word reverse")
else -> throw IllegalArgumentException("weird type to reverse")
}
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
@@ -313,7 +360,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type to sort")
}
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
@@ -323,19 +371,25 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])
@@ -408,118 +462,148 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, address = address)
it += IRInstruction(Opcode.STOREZM, dt, address = address)
}
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = tr.resultReg)
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
}
}
} else {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = tr.resultReg, address = address)
if(dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
}
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
if(dt==IRDataType.FLOAT) {
addToResult(result, valueTr, -1, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
}
} else {
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
}
}
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPoke(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
return if(dt==IRDataType.FLOAT) {
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.nextFreeFloat()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
}
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val address = call.args[0]
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
}
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = tr.resultReg)
}
}
} else {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
val tr = exprGen.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
}
}
}
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
val valueTr = exprGen.translateExpression(value)
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
}
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPeekW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
return if(address is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
val addressNum = address.number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, address = address)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
}
pokeM(result, addressNum, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val addressTr = exprGen.translateExpression(address)
addToResult(result, addressTr, addressTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
}
private fun funcPeek(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
}
pokeI(result, addressTr.resultReg, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
@@ -531,6 +615,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return exprGen.translateExpression(call.args.single())
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
// ....To be more strict, maybe we should introduce a new result register that is of type .b?
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -538,25 +623,177 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
}
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall): ExpressionCodeResult {
val vmDt = irType(call.args[0].type)
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(opcode, vmDt, reg1 = tr.resultReg)
val arg = call.args[0]
val vmDt = irType(arg.type)
val opcodeMemAndReg = when(call.name) {
"rol" -> Opcode.ROXLM to Opcode.ROXL
"ror" -> Opcode.ROXRM to Opcode.ROXR
"rol2" -> Opcode.ROLM to Opcode.ROL
"ror2" -> Opcode.RORM to Opcode.ROR
else -> throw AssemblyError("wrong func")
}
result += assignRegisterTo(call.args[0], tr.resultReg)
val ident = arg as? PtIdentifier
if(ident!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = ident.name), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val memAddr = (arg as? PtMemoryByte)?.address?.asConstInteger()
if(memAddr!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
val variable = arr.variable.name
val itemsize = codeGen.program.memsizer.memorySize(arr.type)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = index*itemsize), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val opcode = opcodeMemAndReg.second
val saveCarry = opcode in OpcodesThatDependOnCarry && !arg.isSimple()
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
val tr = exprGen.translateExpression(arg)
addToResult(result, tr, tr.resultReg, -1)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
result += assignRegisterTo(arg, tr.resultReg)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val target = call.args[0]
val isConstZeroValue = call.args[1].asConstInteger()==0
when(target) {
is PtIdentifier -> {
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
}
}
}
is PtArrayIndexer -> {
require(!target.usesPointerVariable)
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
}
}
}
}
else {
val eltSize = codeGen.program.memsizer.memorySize(target.type)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
}
}
}
else -> throw AssemblyError("weird target for setlsb/setmsb: $target")
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
@@ -3,10 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.code.core.*
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
@@ -34,7 +31,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val code = IRCodeChunk(null, null)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number.toFloat())
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
}
else {
@@ -69,10 +66,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val code = IRCodeChunk(null, null)
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
ExpressionCodeResult(code, vmDt, resultRegister, -1)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
if(expr.identifier.type in SplitWordArrayTypes) {
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
if(eltSize>1)
it += IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=indexTr.resultReg, immediate = eltSize)
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
}
}
ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
is PtMemoryByte -> {
val result = mutableListOf<IRCodeChunkBase>()
@@ -107,6 +122,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -115,6 +131,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -126,6 +143,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -136,7 +154,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
DataType.ARRAY_F -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
}
}
@@ -150,7 +180,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(arrayIx.usesPointerVariable) {
if(eltSize!=1)
throw AssemblyError("non-array var indexing requires bytes dt")
if(arrayIx.index.type!=DataType.UBYTE)
if(arrayIx.index.type !in ByteDatatypes)
throw AssemblyError("non-array var indexing requires bytes index")
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
@@ -170,18 +200,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val memOffset = (arrayIx.index as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb", symbolOffset = memOffset)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb", symbolOffset = memOffset)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
}
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
@@ -189,14 +219,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize).toString()
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.nextFreeFloat()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = "$arrayVarSymbol+$memOffset"), null)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
else {
resultRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = "$arrayVarSymbol+$memOffset"), null)
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
} else {
val tr = translateExpression(arrayIx.index)
@@ -347,9 +377,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt)
"&" -> operatorAnd(binExpr, vmDt)
"^" -> operatorXor(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
@@ -366,6 +398,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -386,8 +419,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@@ -397,6 +429,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StRomSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -409,6 +442,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += tr.chunks
}
// return value
var statusFlagResult: Statusflag? = null
val returnRegSpec = if(fcall.void) null else {
if(callTarget.returns.isEmpty())
null
@@ -417,8 +451,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
else {
statusFlagResult = returns.register.statusflag
val returnRegister = if(statusFlagResult==null) codeGen.registers.nextFree() else -1
FunctionCallArgs.RegSpec(returnIrType, returnRegister, returns.register)
}
} else {
// multiple return values: take the first *register* (not status flag) return value and ignore the rest.
val returns = callTarget.returns.first { it.register.registerOrPair!=null }
@@ -436,12 +473,48 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
if(statusFlagResult!=null && returnRegSpec!=null) {
// assign status flag bit to the return value register
finalReturnRegister = returnRegSpec.registerNum
if(finalReturnRegister<0)
finalReturnRegister = codeGen.registers.nextFree()
when(statusFlagResult) {
Statusflag.Pc -> {
addInstr(result, IRInstruction(Opcode.SCS, returnRegSpec.dt, reg1=finalReturnRegister), null)
}
else -> {
val branchOpcode = when(statusFlagResult) {
Statusflag.Pc -> throw AssemblyError("carry should be treated separately")
Statusflag.Pz -> Opcode.BSTEQ
Statusflag.Pv -> Opcode.BSTVS
Statusflag.Pn -> Opcode.BSTNEG
}
val setLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(branchOpcode, labelSymbol = setLabel)
it += IRInstruction(Opcode.LOAD, returnRegSpec.dt, reg1=finalReturnRegister, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel)
}
result += IRCodeChunk(setLabel, null).also {
it += IRInstruction(Opcode.LOAD, returnRegSpec.dt, reg1=finalReturnRegister, immediate = 1)
}
result += IRCodeChunk(endLabel, null)
}
}
}
}
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum)
ExpressionCodeResult(result, returnRegSpec!!.dt, -1, finalReturnRegister)
else
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
}
else -> throw AssemblyError("invalid node type")
}
@@ -543,14 +616,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val resultRegister = codeGen.registers.nextFree()
val valueReg = codeGen.registers.nextFree()
val label = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1), null)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
if (notEquals) {
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
} else {
addInstr(result, IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
it += if (notEquals)
IRInstruction(Opcode.BSTNE, labelSymbol = label)
else
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
result += IRCodeChunk(label, null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -631,37 +706,61 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType, bitwise: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(!bitwise && !binExpr.right.isSimple()) {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ANDR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
return ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ANDR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: IRDataType, bitwise: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(!bitwise && !binExpr.right.isSimple()) {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ORR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
return ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ORR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}
@@ -690,7 +789,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.divideByConstFloat(tr.resultFpReg, factor)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -746,13 +845,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(constFactorLeft!=null) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorLeft.number.toFloat()
val factor = constFactorLeft.number
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else if(constFactorRight!=null) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -800,7 +899,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -855,7 +954,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -905,8 +1004,38 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
,null)
IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, labelSymbol = symbol),null)
return result
}
internal fun operatorLogicalAndInplace(knownAddress: Int?, symbol: String?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(operand)
if(!operand.isSimple()) {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
val inplaceReg = codeGen.registers.nextFree()
val shortcutLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += if(knownAddress!=null)
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = knownAddress)
else
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel)
}
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
result += IRCodeChunk(shortcutLabel, null)
} else {
// normal evaluation, it is *likely* shorter and faster because of the simple operands.
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, labelSymbol = symbol),null)
}
return result
}
@@ -917,8 +1046,38 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
, null)
IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
return result
}
internal fun operatorLogicalOrInplace(knownAddress: Int?, symbol: String?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(operand)
if(!operand.isSimple()) {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
val inplaceReg = codeGen.registers.nextFree()
val shortcutLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += if(knownAddress!=null)
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = knownAddress)
else
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel)
}
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
result += IRCodeChunk(shortcutLabel, null)
} else {
// normal evaluation, it is *likely* shorter and faster because of the simple operands.
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, if(knownAddress!=null)
IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, address = knownAddress)
else
IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
}
return result
}
@@ -927,7 +1086,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.divideByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -976,7 +1135,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null) {
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.multiplyByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -1301,7 +1460,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// in-place modify a memory location
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
@@ -1312,7 +1471,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// in-place modify a symbol (variable)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
@@ -18,7 +18,6 @@ class IRCodeGen(
private val expressionEval = ExpressionGen(this)
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
private val assignmentGen = AssignmentGen(this, expressionEval)
private var irSymbolTable: IRSymbolTable = IRSymbolTable(null)
internal val registers = RegisterPool()
fun generate(): IRProgram {
@@ -26,7 +25,7 @@ class IRCodeGen(
moveAllNestedSubroutinesToBlockScope()
verifyNameScoping(program, symbolTable)
irSymbolTable = IRSymbolTable(symbolTable)
val irSymbolTable = IRSymbolTable.fromStDuringCodegen(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
// collect global variables initializers
@@ -144,19 +143,10 @@ class IRCodeGen(
(idx, instr) ->
val symbolExpr = instr.labelSymbol
if(symbolExpr!=null) {
val symbol: String
val index: UInt
if('+' in symbolExpr) {
val operands = symbolExpr.split('+', )
symbol = operands[0]
index = operands[1].toUInt()
} else {
symbol = symbolExpr
index = 0u
}
val target = symbolTable.flat[symbol]
val index = instr.labelSymbolOffset ?: 0
val target = symbolTable.flat[symbolExpr]
if (target is StMemVar) {
replacements.add(Triple(chunk, idx, target.address+index))
replacements.add(Triple(chunk, idx, target.address+index.toUInt()))
}
}
}
@@ -283,15 +273,10 @@ class IRCodeGen(
else -> TODO("missing codegen for $node")
}
chunks.forEach { chunk ->
require(chunk.isNotEmpty() || chunk.label != null) {
"chunk should have instructions and/or a label"
}
}
val nonEmptyChunks = chunks.filter { it.isNotEmpty() || it.label != null }
nonEmptyChunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
return chunks
return nonEmptyChunks
}
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
@@ -321,7 +306,7 @@ class IRCodeGen(
}
addInstr(result, branchIns, null)
} else {
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.name
val label = goto.identifier!!.name
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
@@ -415,13 +400,19 @@ class IRCodeGen(
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = endLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = choiceLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
}
}
}
}
@@ -464,7 +455,7 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1 = tmpReg, immediate = 0, labelSymbol = endLabel)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
@@ -484,15 +475,19 @@ class IRCodeGen(
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegLsb, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
it += IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength, labelSymbol = loopLabel)
if(iterableLength!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = iterableLength)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else -> {
@@ -507,7 +502,13 @@ class IRCodeGen(
}
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes, labelSymbol = loopLabel), null)
result += IRCodeChunk(null, null).also {
if(lengthBytes!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = lengthBytes)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
}
}
@@ -555,8 +556,11 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
addInstr(result, IRInstruction(Opcode.BEQR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol = labelAfterFor), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
@@ -601,8 +605,8 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.XOR, loopvarDtIr, reg1 = 999, immediate = 111)
chunk2 += IRInstruction(Opcode.BNE, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped, labelSymbol = loopLabel)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
result += chunk2
return result
}
@@ -691,23 +695,23 @@ class IRCodeGen(
return code
}
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
internal fun multiplyByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
code += if(factor==0f) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0f)
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
if(factor==0f) {
if(factor==0.0) {
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, address = knownAddress)
else
@@ -788,25 +792,25 @@ class IRCodeGen(
return code
}
internal fun divideByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
internal fun divideByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
code += if(factor==0f) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Float.MAX_VALUE)
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Double.MAX_VALUE)
} else {
IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
if(factor==0f) {
if(factor==0.0) {
val maxvalueReg = registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Float.MAX_VALUE)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Double.MAX_VALUE)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, address = knownAddress)
else
@@ -961,21 +965,29 @@ class IRCodeGen(
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
)
val gotoOpcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
when(condition.operator) {
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTEQ)
}
"!=" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
else -> {
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
return result
} else {
@@ -990,6 +1002,11 @@ class IRCodeGen(
}
}
private fun branchInstr(goto: PtJump, branchOpcode: Opcode) = if (goto.address != null)
IRInstruction(branchOpcode, address = goto.address?.toInt())
else
IRInstruction(branchOpcode, labelSymbol = goto.identifier!!.name)
private fun ifZeroIntThenJump(
result: MutableList<IRCodeChunkBase>,
ifElse: PtIfElse,
@@ -1000,21 +1017,33 @@ class IRCodeGen(
val condition = ifElse.condition as PtBinaryExpression
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
val lastInstruction = leftTr.chunks.last().instructions.lastOrNull()
val requireCompareZero = lastInstruction?.opcode !in OpcodesThatSetStatusbits
when(condition.operator) {
"==" -> {
if(requireCompareZero)
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
if(requireCompareZero)
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
val opcode = when (condition.operator) {
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
private fun ifNonZeroIntThenJump(
@@ -1026,49 +1055,59 @@ class IRCodeGen(
) {
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if (goto.address != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
throw AssemblyError("expected condition")
// val tr = expressionEval.translateExpression(ifElse.condition)
// result += tr.chunks
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0) // was redundant CMP most likely
// it += branchInstr(goto, Opcode.BSTNE)
// }
} else {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode: Opcode
val number = (condition.right as? PtNumber)?.number?.toInt()
if(number!=null) {
val firstReg = leftTr.resultReg
opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
when(condition.operator) {
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
val secondReg: Int
val opcode: Opcode
var useCmp = false
when (condition.operator) {
"==" -> {
opcode = Opcode.BEQR
useCmp = true
opcode = Opcode.BSTEQ
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"!=" -> {
opcode = Opcode.BNER
useCmp = true
opcode = Opcode.BSTNE
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
@@ -1096,12 +1135,18 @@ class IRCodeGen(
}
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
if(useCmp) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, irDtLeft, reg1 = firstReg, reg2 = secondReg)
it += branchInstr(goto, opcode)
}
} else {
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
}
}
}
}
@@ -1109,6 +1154,7 @@ class IRCodeGen(
private fun translateIfElseZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
val compResultReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as PtBinaryExpression
@@ -1119,16 +1165,22 @@ class IRCodeGen(
addToResult(result, leftTr, -1, leftTr.resultFpReg)
result += IRCodeChunk(null, null).also {
val rightFpReg = registers.nextFreeFloat()
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0.0)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
}
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
} else {
@@ -1137,13 +1189,19 @@ class IRCodeGen(
val tr = expressionEval.translateExpression(condition.left)
compResultReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if (signed) Opcode.BGES else Opcode.BGE
">" -> if (signed) Opcode.BLES else Opcode.BLE
"<=" -> if (signed) Opcode.BGTS else Opcode.BGT
">=" -> if (signed) Opcode.BLTS else Opcode.BLT
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if (signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if (signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if (signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if (signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("weird operator")
}
}
@@ -1151,14 +1209,26 @@ class IRCodeGen(
if(ifElse.elseScope.children.isEmpty()) {
// just if
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
} else {
// if and else
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1169,31 +1239,12 @@ class IRCodeGen(
private fun translateIfElseNonZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranchOpcode: Opcode
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
if(irDtLeft==IRDataType.FLOAT)
throw AssemblyError("condition value should not be float")
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if(ifElse.elseScope.children.isNotEmpty()) {
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
result += IRCodeChunk(afterIfLabel, null)
} else {
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
return result
throw AssemblyError("if-else condition is not a binaryexpression, should have been converted?")
} else {
if (irDtLeft == IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
@@ -1201,35 +1252,35 @@ class IRCodeGen(
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val compResultReg = registers.nextFree()
addInstr(
result,
IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
reg1 = compResultReg,
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
),
null
)
val elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1237,11 +1288,13 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1252,26 +1305,34 @@ class IRCodeGen(
addToResult(result, leftTr, leftTr.resultReg, -1)
val number = (condition.right as? PtNumber)?.number?.toInt()
if (number!=null) {
elseBranchOpcode = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if(signed) Opcode.BGES else Opcode.BGE
">" -> if(signed) Opcode.BLES else Opcode.BLE
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if(signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if(signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if(signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = elseLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1279,52 +1340,56 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = afterIfLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
val elseBranch: Opcode
var useCmp = false
addToResult(result, rightTr, rightTr.resultReg, -1)
when (condition.operator) {
"==" -> {
elseBranchOpcode = Opcode.BNER
useCmp = true
elseBranch = Opcode.BSTNE
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"!=" -> {
elseBranchOpcode = Opcode.BEQR
useCmp = true
elseBranch = Opcode.BSTEQ
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"<" -> {
// else part when left >= right
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">" -> {
// else part when left <= right --> right >= left
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
"<=" -> {
// else part when left > right
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">=" -> {
// else part when left < right --> right > left
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
@@ -1334,13 +1399,14 @@ class IRCodeGen(
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = elseLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = elseLabel), null)
}
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1348,13 +1414,14 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = afterIfLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null)
}
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1375,21 +1442,21 @@ class IRCodeGen(
when(postIncrDecr.operator) {
"++" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
result += IRCodeChunk(skipLabel, null)
}
"--" -> {
val valueReg=registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=valueReg, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=valueReg, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
result += IRCodeChunk(skipLabel, null).also {
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
}
}
else -> throw AssemblyError("weird operator")
@@ -1480,7 +1547,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.STOREIX, irDt, reg1 = dataReg, reg2 = indexReg, labelSymbol = variable)
}
} else {
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = "$variable+$offset"), null)
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = variable, symbolOffset = offset), null)
}
} else {
val indexTr = expressionEval.translateExpression(array.index)
@@ -1525,12 +1592,12 @@ class IRCodeGen(
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BEQ, irDt, reg1=countTr.resultReg, immediate = 0, labelSymbol = skipRepeatLabel), null)
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
it += IRInstruction(Opcode.BNE, irDt, reg1 = countTr.resultReg, immediate = 0, labelSymbol = repeatLabel)
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
result += IRCodeChunk(skipRepeatLabel, null)
return result
@@ -1542,9 +1609,7 @@ class IRCodeGen(
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.generatedLabel != null)
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
else if (jump.identifier != null) {
if (jump.identifier != null) {
val symbol = symbolTable.lookup(jump.identifier!!.name)
if(symbol?.type==StNodeType.MEMVAR || symbol?.type==StNodeType.STATICVAR) {
val jumpReg = registers.nextFree()
@@ -1588,7 +1653,15 @@ class IRCodeGen(
}
private fun translate(block: PtBlock): IRBlock {
val irBlock = IRBlock(block.name, block.address, block.library, block.forceOutput, translate(block.alignment), block.position) // no use for other attributes yet?
val irBlock = IRBlock(block.name, block.library,
IRBlock.Options(
block.options.address,
block.options.forceOutput,
block.options.noSymbolPrefixing,
block.options.veraFxMuls,
block.options.ignoreUnused,
translate(block.options.alignment)
), block.position)
for (child in block.children) {
when(child) {
is PtNop -> { /* nothing */ }
@@ -1636,7 +1709,7 @@ class IRCodeGen(
is PtLabel -> {
irBlock += IRCodeChunk(child.name, null)
}
else -> TODO("weird child node $child")
else -> TODO("weird block child node $child")
}
}
return irBlock
@@ -1660,7 +1733,7 @@ class IRCodeGen(
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
return "prog8_label_gen_$labelSequenceNumber"
return "label_gen_$labelSequenceNumber"
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
@@ -45,7 +45,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val indexedInstructions = chunk1.instructions.withIndex()
.map { IndexedValue(it.index, it.value) }
val changed = removeNops(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| replaceConcatZeroMsbWithExt(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions)
|| removeUselessArithmetic(chunk1, indexedInstructions)
|| removeNeedlessCompares(chunk1, indexedInstructions)
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
@@ -63,6 +64,22 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
irprog.linkChunks() // re-link
}
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.CONCAT) {
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
chunk.instructions.removeAt(idx-1)
changed = true
}
}
}
return changed
}
private fun removeEmptyChunks(sub: IRSubroutine) {
if(sub.chunks.isEmpty())
return
@@ -70,7 +87,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: merge both labels into 1)
If next chunk has label -> label name should be the same, remove original. Otherwise merge both labels into 1.
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
@@ -79,6 +96,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val relabelChunks = mutableListOf<Pair<Int, String>>()
val removeChunks = mutableListOf<Int>()
val replaceLabels = mutableMapOf<String, String>()
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
@@ -92,10 +110,18 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
relabelChunks += Pair(index + 1, chunk.label!!)
removeChunks += index
} else {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
// TODO: merge labels on same chunk
// merge both labels into 1 except if this is the label chunk at the start of the subroutine
if(index>0) {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
removeChunks += index
replaceLabels[chunk.label!!] = nextchunk.label!!
replaceLabels.entries.forEach { (key, value) ->
if (value == chunk.label)
replaceLabels[key] = nextchunk.label!!
}
}
}
}
}
@@ -112,6 +138,25 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
sub.chunks[index] = chunk
}
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
sub.chunks.forEach { chunk ->
chunk.instructions.withIndex().forEach { (idx, instr) ->
instr.labelSymbol?.let {
if(instr.opcode in OpcodesThatBranch) {
replaceLabels.forEach { (from, to) ->
if (it == from) {
chunk.instructions[idx] = instr.copy(labelSymbol = to)
}
else {
val actualPrefix = "$from."
if (it.startsWith(actualPrefix))
chunk.instructions[idx] = instr.copy(labelSymbol = "$to.")
}
}
}
}
}
}
}
private fun joinChunks(sub: IRSubroutine) {
@@ -279,14 +324,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
val previous = indexedInstructions[idx-1].value
if(previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
chunk.instructions.removeAt(idx)
changed = true
} else if(previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
val next = indexedInstructions[idx+1].value
if(next.opcode !in arrayOf(Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTPOS, Opcode.BSTNEG)) {
if(previous.reg1==ins.reg1) {
if (previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
chunk.instructions.removeAt(idx)
changed = true
} else if (previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
val next = indexedInstructions[idx + 1].value
if (next.opcode !in OpcodesThatDependOnCarry) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
}
@@ -363,18 +410,21 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
return false
/*
var changed = false
indexedInstructions.forEach { (idx, ins) ->
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
// TODO: detect multiple float ffrom/fto to the same target, only keep first
// TODO: detect multiple sequential rnd with same reg1, only keep one
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
// TODO: detect multiple same ands, ors; only keep first
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
// ...
}
return changed
*/
}
}
@@ -1,7 +1,6 @@
package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX
import prog8.intermediate.*
@@ -25,18 +24,29 @@ class IRUnusedCodeRemover(
}
private fun pruneSymboltable(blockLabel: String) {
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
blockVars.forEach { stVar ->
irprog. allSubs().flatMap { it.chunks }.forEach { chunk ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
return
return // symbol occurs in an instruction
}
}
}
irprog.st.allVariables().forEach { variable ->
val initValue = variable.onetimeInitializationArrayValue
if(!initValue.isNullOrEmpty()) {
if(initValue.any {
it.addressOfSymbol?.startsWith(blockLabel)==true
})
return // symbol occurs in an initializer value (address-of this symbol)_
}
}
}
irprog.st.removeTree(blockLabel)
}
@@ -50,8 +60,8 @@ class IRUnusedCodeRemover(
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
errors.warn("unused subroutine ${sub.label}", sub.position)
if(!block.options.ignoreUnused) {
errors.info("unused subroutine '${sub.label}'", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
@@ -71,8 +81,8 @@ class IRUnusedCodeRemover(
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRAsmSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
errors.warn("unused asmsubroutine ${sub.label}", sub.position)
if(!block.options.ignoreUnused) {
errors.info("unused subroutine '${sub.label}'", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
@@ -92,7 +102,7 @@ class IRUnusedCodeRemover(
// check if asmsub is called from another asmsub
irprog.blocks.asSequence().forEach { block ->
block.children.filterIsInstance<IRAsmSubroutine>().forEach { sub ->
if (block.forceOutput || block.library)
if (block.options.forceOutput || block.library)
linkedAsmSubs += sub
if (sub.asmChunk.isNotEmpty()) {
allSubs.forEach { (label, asmsub) ->
@@ -157,7 +167,13 @@ class IRUnusedCodeRemover(
it.next?.let { next -> new += next }
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
instr.labelSymbol?.let { label ->
val chunk = allLabeledChunks[label.substringBeforeLast('.')]
if(chunk!=null)
new+=chunk
else
allLabeledChunks[label]?.let { new += it }
}
else
new += instr.branchTarget!!
}
@@ -204,12 +220,20 @@ class IRUnusedCodeRemover(
linkedChunks += chunk
}
// make sure that chunks that are only used as a prefix of a label, are also marked as linked
linkedChunks.toList().forEach { chunk ->
chunk.instructions.forEach {
if(it.labelSymbol!=null) {
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
allLabeledChunks[chunkName]?.let { linkedChunks+=it }
}
}
}
return removeUnlinkedChunks(linkedChunks)
}
private fun removeUnlinkedChunks(
linkedChunks: Set<IRCodeChunkBase>
): Int {
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
var numRemoved = 0
irprog.foreachSub { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
+13 -3
View File
@@ -16,6 +16,8 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -25,11 +27,11 @@ internal object DummyStringEncoder : IStringEncoding {
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
IErrorReporter {
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
val infos = mutableListOf<String>()
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
@@ -43,6 +45,12 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
infos.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
@@ -50,10 +58,11 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
clear()
}
@@ -62,5 +71,6 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
fun clear() {
errors.clear()
warnings.clear()
infos.clear()
}
}
+10 -11
View File
@@ -8,7 +8,7 @@ import prog8.intermediate.*
class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="main.start")
val block = IRBlock("main", null, false, false, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
chunks.forEach { sub += it }
block += sub
@@ -18,12 +18,13 @@ class TestIRPeepholeOpt: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
val prog = IRProgram("test", IRSymbolTable(null), options, target)
val prog = IRProgram("test", IRSymbolTable(), options, target)
prog.addBlock(block)
prog.linkChunks()
prog.validate()
@@ -52,9 +53,9 @@ class TestIRPeepholeOpt: FunSpec({
test("remove jmp to label below") {
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
c2 += IRInstruction(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
@@ -66,15 +67,13 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 4
irProg.chunks().size shouldBe 3
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label"
irProg.chunks()[2].label shouldBe "label2"
irProg.chunks()[3].label shouldBe "label3"
irProg.chunks()[1].label shouldBe "label2"
irProg.chunks()[2].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe true
irProg.chunks()[2].isEmpty() shouldBe false
irProg.chunks()[3].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe false
irProg.chunks()[2].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP
+19 -14
View File
@@ -19,6 +19,7 @@ class TestVmCodeGen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
@@ -41,7 +42,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -91,7 +92,7 @@ class TestVmCodeGen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -120,7 +121,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -183,7 +184,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -242,7 +243,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -250,7 +251,7 @@ class TestVmCodeGen: FunSpec({
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
@@ -258,7 +259,7 @@ class TestVmCodeGen: FunSpec({
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
if2.add(PtNodeGroup())
sub.add(if2)
block.add(sub)
@@ -289,7 +290,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -352,7 +353,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -411,7 +412,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -419,7 +420,7 @@ class TestVmCodeGen: FunSpec({
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp1.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
@@ -427,7 +428,7 @@ class TestVmCodeGen: FunSpec({
cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp2.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
if2.add(PtNodeGroup())
sub.add(if2)
block.add(sub)
@@ -451,7 +452,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
@@ -466,7 +467,11 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
val callInstr = irChunks.single().instructions.single()
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}
+3 -2
View File
@@ -27,16 +27,17 @@ dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+1
View File
@@ -11,5 +11,6 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -1,17 +1,19 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
import prog8.code.core.Position
import kotlin.math.*
class ConstExprEvaluator {
fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): Expression {
fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): NumericLiteral {
try {
return when(operator) {
"+" -> plus(left, right)
@@ -19,9 +21,9 @@ class ConstExprEvaluator {
"*" -> multiply(left, right)
"/" -> divide(left, right)
"%" -> remainder(left, right)
"&" -> bitwiseand(left, right)
"|" -> bitwiseor(left, right)
"^" -> bitwisexor(left, right)
"&" -> bitwiseAnd(left, right)
"|" -> bitwiseOr(left, right)
"^" -> bitwiseXor(left, right)
"<" -> NumericLiteral.fromBoolean(left < right, left.position)
">" -> NumericLiteral.fromBoolean(left > right, left.position)
"<=" -> NumericLiteral.fromBoolean(left <= right, left.position)
@@ -30,6 +32,9 @@ class ConstExprEvaluator {
"!=" -> NumericLiteral.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
"and" -> logicalAnd(left, right)
"or" -> logicalOr(left, right)
"xor" -> logicalXor(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
}
} catch (ax: FatalAstException) {
@@ -37,7 +42,7 @@ class ConstExprEvaluator {
}
}
private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): Expression {
private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): NumericLiteral {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left >> $amount", left.position)
val result =
@@ -48,52 +53,81 @@ class ConstExprEvaluator {
return NumericLiteral(left.type, result.toDouble(), left.position)
}
private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): Expression {
private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): NumericLiteral {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt())
return NumericLiteral(left.type, result.toDouble(), left.position)
// when(left.type) {
// DataType.BOOL -> result = result and 1
// DataType.UBYTE -> result = result and 255
// DataType.BYTE -> result = result.toByte().toInt()
// DataType.UWORD -> result = result and 65535
// DataType.WORD -> result = result.toShort().toInt()
// else -> { /* keep as it is */ }
// }
return NumericLiteral.optimalNumeric(result.toDouble(), left.position)
}
private fun bitwisexor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
}
private fun bitwiseor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
}
private fun bitwiseand(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
}
private fun logicalAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
NumericLiteral.fromBoolean(left.asBooleanValue and right.asBooleanValue, left.position)
private fun logicalOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
NumericLiteral.fromBoolean(left.asBooleanValue or right.asBooleanValue, left.position)
private fun logicalXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
NumericLiteral.fromBoolean(left.asBooleanValue xor right.asBooleanValue, left.position)
private fun plus(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot add $left and $right"
return when (left.type) {
@@ -206,4 +240,154 @@ class ConstExprEvaluator {
else -> throw ExpressionError(error, left.position)
}
}
fun evaluate(call: FunctionCallExpression, program: Program): NumericLiteral? {
if(call.target.nameInSource.size!=2)
return null // likely a builtin function, or user function, these get evaluated elsewhere
val constArgs = call.args.mapNotNull { it.constValue(program) }
if(constArgs.size!=call.args.size)
return null
return when(call.target.nameInSource[0]) {
"math" -> evalMath(call, constArgs)
"floats" -> evalFloats(call, constArgs)
"string" -> evalString(call, constArgs)
else -> null
}
}
private fun evalFloats(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
val result= when(func.target.nameInSource[1]) {
"pow" -> args[0].number.pow(args[1].number)
"sin" -> sin(args[0].number)
"cos" -> cos(args[0].number)
"tan" -> tan(args[0].number)
"atan" -> atan(args[0].number)
"ln" -> ln(args[0].number)
"log2" -> log2(args[0].number)
"rad" -> args[0].number/360.0 * 2 * PI
"deg" -> args[0].number/ 2 / PI * 360.0
"round" -> round(args[0].number)
"floor" -> floor(args[0].number)
"ceil" -> ceil(args[0].number)
"minf", "min" -> min(args[0].number, args[1].number)
"maxf", "max" -> max(args[0].number, args[1].number)
"clampf", "clamp" -> {
var value = args[0].number
val minimum = args[1].number
val maximum = args[2].number
if(value<minimum)
value=minimum
if(value<maximum)
value
else
maximum
}
else -> null
}
return if(result==null)
null
else
NumericLiteral(DataType.FLOAT, result, func.position)
}
private fun evalMath(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
return when(func.target.nameInSource[1]) {
"sin8u" -> {
val value = truncate(128.0 + 127.5 * sin(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"cos8u" -> {
val value = truncate(128.0 + 127.5 * cos(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"sin8" -> {
val value = truncate(127.0 * sin(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"cos8" -> {
val value = truncate(127.0 * cos(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"sinr8u" -> {
val value = truncate(128.0 + 127.5 * sin(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"cosr8u" -> {
val value = truncate(128.0 + 127.5 * cos(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"sinr8" -> {
val value = truncate(127.0 * sin(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"cosr8" -> {
val value = truncate(127.0 * cos(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"log2" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"log2w" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(DataType.UWORD, value, func.position)
}
"atan2" -> {
val x1f = args[0].number
val y1f = args[1].number
val x2f = args[2].number
val y2f = args[3].number
var radians = atan2(y2f-y1f, x2f-x1f)
if(radians<0)
radians+=2*PI
NumericLiteral(DataType.UWORD, floor(radians/2.0/PI*256.0), func.position)
}
"diff" -> {
val n1 = args[0].number
val n2 = args[1].number
val value = if(n1>n2) n1-n2 else n2-n1
NumericLiteral(DataType.UBYTE, value, func.position)
}
"diffw" -> {
val n1 = args[0].number
val n2 = args[1].number
val value = if(n1>n2) n1-n2 else n2-n1
NumericLiteral(DataType.UWORD, value, func.position)
}
else -> null
}
}
private fun evalString(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
return when(func.target.nameInSource[1]) {
"isdigit" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 48..57, func.position)
}
"isupper" -> {
// shifted petscii has 2 ranges that contain the upper case letters... 97-122 and 193-218
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 97..122 || char in 193..218, func.position)
}
"islower" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 65..90, func.position)
}
"isletter" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 65..90 || char in 97..122 || char in 193..218, func.position)
}
"isspace" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
}
"isprint" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 32..127 || char>=160, func.position)
}
else -> null
}
}
}
@@ -2,7 +2,6 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.ForLoop
@@ -13,18 +12,27 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import kotlin.math.floor
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
class ConstantFoldingOptimizer(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val evaluator = ConstExprEvaluator()
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
val constAddr = addressOf.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(addressOf, constAddr, parent))
}
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
}
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
@@ -79,6 +87,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
// mutiply a string.
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
}
@@ -86,7 +96,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
if(leftExpr!=null) {
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
if(leftExpr?.constValue(program) != null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
@@ -133,9 +144,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
}
val evaluator = ConstExprEvaluator()
// const fold when both operands are a const
// const fold when both operands are a const.
// if in a chained comparison, that one has to be desugared first though.
if(leftconst != null && rightconst != null) {
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
modifications += IAstModification.ReplaceNode(expr, result, parent)
@@ -183,10 +193,12 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} else if (leftBinExpr.operator=="/") {
// (X / C2) * rightConst --> X * (rightConst/C2)
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
if(expr.inferType(program).istype(DataType.FLOAT)) {
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
}
}
@@ -194,7 +206,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null && leftBinExpr.operator=="/") {
// (X / C1) / C2 --> X / (C1*C2)
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -226,10 +238,14 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
if (c1 != null && c2 != null && c1==c2) {
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C (only if types of X and Y are the same!)
val xDt = leftBinExpr.left.inferType(program)
val yDt = rightBinExpr.left.inferType(program)
if(xDt==yDt) {
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
}
}
@@ -310,8 +326,13 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
else
noModifications
else {
val const2 = evaluator.evaluate(functionCallExpr, program)
return if(const2!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
else
noModifications
}
}
override fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> {
@@ -385,7 +406,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
}
return noModifications
@@ -416,6 +437,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val constValue = typecast.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
@@ -525,7 +551,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program).istype(DataType.FLOAT)) {
// division optimizations only valid for floats
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
@@ -628,5 +655,4 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return null
}
}
}
@@ -1,5 +1,7 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
@@ -9,10 +11,17 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.CallGraph
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance)
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
class VarConstantValueTypeAdjuster(
private val program: Program,
private val options: CompilationOptions,
private val errors: IErrorReporter
) : AstWalker() {
private val callGraph by lazy { CallGraph(program) }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@@ -25,9 +34,10 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
&& declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type == DataType.FLOAT) {
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
@@ -37,6 +47,73 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
errors.err(x.message, x.position)
}
// replace variables by constants, if possible
if(options.optimize) {
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || decl.datatype !in NumericDatatypes)
return noModifications
if (decl.value != null && decl.value!!.constValue(program) == null)
return noModifications
val usages = callGraph.usages(decl)
val (writes, reads) = usages
.partition {
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|| it.parent is AssignTarget
|| it.parent is ForLoop
|| it.parent is AddressOf
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
}
val singleAssignment =
writes.singleOrNull()?.parent?.parent as? Assignment ?: writes.singleOrNull()?.parent as? Assignment
if (singleAssignment == null) {
if (writes.isEmpty()) {
if(reads.isEmpty()) {
// variable is never used AT ALL so we just remove it altogether
if("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
}
val declValue = decl.value?.constValue(program)
if (declValue != null) {
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
)
}
}
} else {
if (singleAssignment.origin == AssignmentOrigin.VARINIT && singleAssignment.value.constValue(program) != null) {
if(reads.isEmpty()) {
// variable is never used AT ALL so we just remove it altogether, including the single assignment
if("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(
IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
)
}
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
)
}
}
/*
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
}
}
*/
}
return noModifications
}
@@ -195,6 +272,14 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
val constValue = addressOf.constValue(program)
if(constValue!=null) {
return listOf(IAstModification.ReplaceNode(addressOf, constValue, parent))
}
return noModifications
}
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
@@ -227,16 +312,20 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
}
return when (cval.type) {
in NumericDatatypes -> listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
when (cval.type) {
in NumericDatatypes -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
)
)
}
in PassByReferenceDatatypes -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> noModifications
else -> return noModifications
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
@@ -244,14 +333,23 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray && decl.type==VarDeclType.MEMORY && decl.value !is IdentifierReference) {
val memaddr = decl.value?.constValue(program)
if(memaddr!=null && memaddr !== decl.value) {
return listOf(IAstModification.SetExpression(
{ decl.value = it }, memaddr, decl
))
}
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.isArray){
val arraysize = decl.arraysize
if(arraysize==null) {
@@ -276,107 +374,10 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
in ArrayDatatypes -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
}
else -> {
// nothing to do for this type
@@ -386,4 +387,112 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
}
}
else -> return null
}
return null
}
}
@@ -13,7 +13,6 @@ import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
@@ -66,30 +65,36 @@ class ExpressionSimplifier(private val program: Program,
)
}
if(elsepart.statements.singleOrNull() is Jump) {
val invertedCondition = invertCondition(ifElse.condition)
if(invertedCondition!=null) {
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
val invertedCondition = invertCondition(ifElse.condition, program)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
}
if(compTarget.name!=VMTarget.NAME) {
val booleanCondition = ifElse.condition as? BinaryExpression
if(booleanCondition!=null && booleanCondition.operator=="&") {
// special optimization of WORD & $ff00 -> just and the msb of WORD with $ff
val rightNum = booleanCondition.right as? NumericLiteral
if(rightNum!=null && rightNum.type==DataType.UWORD && (rightNum.number.toInt() and 0x00ff)==0) {
val booleanCondition = ifElse.condition as? BinaryExpression
if(booleanCondition!=null && booleanCondition.operator=="&") {
val rightNum = booleanCondition.right as? NumericLiteral
if (rightNum!=null && rightNum.type==DataType.UWORD) {
if ((rightNum.number.toInt() and 0x00ff) == 0) {
// if WORD & $xx00 -> if msb(WORD) & $xx
val msb = BuiltinFunctionCall(IdentifierReference(listOf("msb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
val bytevalue = NumericLiteral(DataType.UBYTE, (rightNum.number.toInt() shr 8).toDouble(), booleanCondition.right.position)
return listOf(
IAstModification.ReplaceNode(booleanCondition.left, msb, booleanCondition),
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
}
else if ((rightNum.number.toInt() and 0xff00) == 0) {
// if WORD & $00ff -> if lsb(WORD) & $ff
val lsb = BuiltinFunctionCall(IdentifierReference(listOf("lsb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
val bytevalue = NumericLiteral(DataType.UBYTE, rightNum.number, booleanCondition.right.position)
return listOf(
IAstModification.ReplaceNode(booleanCondition.left, lsb, booleanCondition),
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
}
}
}
@@ -97,6 +102,10 @@ class ExpressionSimplifier(private val program: Program,
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val newExpr = applyAbsorptionLaws(expr)
if(newExpr!=null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
@@ -158,8 +167,8 @@ class ExpressionSimplifier(private val program: Program,
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteral(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
}
} else {
// Y*X - X -> X*(Y - 1)
@@ -168,8 +177,8 @@ class ExpressionSimplifier(private val program: Program,
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteral(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
val replacement = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
}
}
} else if (rightBinExpr?.operator == "*") {
@@ -180,8 +189,8 @@ class ExpressionSimplifier(private val program: Program,
val y = determineY(x, rightBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteral.optimalInteger(1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
}
}
}
@@ -270,7 +279,7 @@ class ExpressionSimplifier(private val program: Program,
// simplify when a term is constant and directly determines the outcome
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
val newExpr2 = when (expr.operator) {
"|" -> {
when {
leftVal?.number==0.0 -> expr.right
@@ -315,33 +324,102 @@ class ExpressionSimplifier(private val program: Program,
}
if(rightVal!=null && leftDt==DataType.BOOL) {
// see if we can replace comparison against true/1 with simpler comparison against zero
if (expr.operator == "==") {
if (rightVal.number == 1.0) {
val zero = NumericLiteral(DataType.UBYTE, 0.0, expr.right.position)
return listOf(
IAstModification.SetExpression({expr.operator="!="}, expr, parent),
IAstModification.ReplaceNode(expr.right, zero, expr)
)
}
}
if (expr.operator == "!=") {
if (rightVal.number == 1.0) {
val zero = NumericLiteral(DataType.UBYTE, 0.0, expr.right.position)
return listOf(
IAstModification.SetExpression({expr.operator="=="}, expr, parent),
IAstModification.ReplaceNode(expr.right, zero, expr)
)
// boolean compare against a number -> just keep the boolean, no compare
if(expr.operator=="==" || expr.operator=="!=") {
val test = if (expr.operator == "==") rightVal.asBooleanValue else !rightVal.asBooleanValue
return if (test) {
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
} else {
listOf(IAstModification.ReplaceNode(expr, invertCondition(expr.left, program), parent))
}
}
}
if(newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
if(newExpr2 != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr2, parent))
if (rightVal!=null && (expr.operator == "==" || expr.operator == "!=")) {
val bitwise = expr.left as? BinaryExpression
if(bitwise!=null && bitwise.operator=="&" && bitwise.inferType(program).isWords) {
val andNum = (bitwise.right as? NumericLiteral)?.number?.toInt()
if (andNum!=null) {
if ((andNum and 0x00ff) == 0) {
// (WORD & $xx00)==y -> (msb(WORD) & $xx)==y
val msb = BuiltinFunctionCall(IdentifierReference(listOf("msb"), bitwise.left.position), mutableListOf(bitwise.left), bitwise.left.position)
val bytevalue = NumericLiteral(DataType.UBYTE, (andNum shr 8).toDouble(), bitwise.right.position)
val rightvalByte = NumericLiteral(DataType.UBYTE, (rightVal.number.toInt() shr 8).toDouble(), rightVal.position)
return listOf(
IAstModification.ReplaceNode(bitwise.left, msb, bitwise),
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
)
}
else if((andNum and 0xff00) == 0) {
// (WORD & $00xx)==y -> (lsb(WORD) & $xx)==y
val lsb = BuiltinFunctionCall(IdentifierReference(listOf("lsb"), bitwise.left.position), mutableListOf(bitwise.left), bitwise.left.position)
val bytevalue = NumericLiteral(DataType.UBYTE, andNum.toDouble(), bitwise.right.position)
val rightvalByte = NumericLiteral(DataType.UBYTE, (rightVal.number.toInt() and 255).toDouble(), rightVal.position)
return listOf(
IAstModification.ReplaceNode(bitwise.left, lsb, bitwise),
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
)
}
}
}
}
return noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="not") {
// not X <compare> Y -> X <invertedcompare> Y
val binExpr = expr.expression as? BinaryExpression
if(binExpr!=null) {
val invertedOperator = invertedComparisonOperator(binExpr.operator)
if(invertedOperator!=null) {
val inverted = BinaryExpression(binExpr.left, invertedOperator, binExpr.right, binExpr.position)
return listOf(IAstModification.ReplaceNode(expr, inverted, parent))
}
}
}
return noModifications
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
val rightB = expr.right as? BinaryExpression
if(rightB!=null) {
// absorption laws: a or (a and b) --> a, a and (a or b) --> a
if(expr.operator=="or" && rightB.operator=="and") {
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
return expr.left
}
}
else if(expr.operator=="and" && rightB.operator=="or") {
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
return expr.left
}
}
}
val leftB = expr.left as? BinaryExpression
if(leftB!=null) {
// absorption laws: (a and b) or a --> a, (a or b) and a --> a
if(expr.operator=="or" && leftB.operator=="and") {
if(expr.right isSameAs leftB.left || expr.left isSameAs leftB.right) {
return expr.right
}
}
else if(expr.operator=="and" && leftB.operator=="or") {
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
return expr.right
}
}
}
return null
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
if(functionCallExpr.args.isEmpty())
@@ -349,10 +427,15 @@ class ExpressionSimplifier(private val program: Program,
val arg = functionCallExpr.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless lsb() of byte value that was typecasted to word
if (valueDt istype DataType.UBYTE) {
// useless lsb() of ubyte value
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
}
else if (valueDt istype DataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
} else {
if(arg is IdentifierReference && arg.nameInSource.size==2
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
@@ -361,10 +444,15 @@ class ExpressionSimplifier(private val program: Program,
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
}
val argDt = arg.inferType(program)
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
if (argDt istype DataType.UBYTE) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg, parent))
}
else if (argDt istype DataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
}
else if(functionCallExpr.target.nameInSource == listOf("msb")) {
@@ -405,6 +493,14 @@ class ExpressionSimplifier(private val program: Program,
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
else if(functionCallExpr.target.nameInSource == listOf("string", "contains")) {
val target = (functionCallExpr.args[0] as? IdentifierReference)?.targetVarDecl(program)
if(target?.value is StringLiteral) {
errors.info("for actual strings, use a regular containment check instead: 'char in string'", functionCallExpr.position)
val contains = ContainmentCheck(functionCallExpr.args[1], functionCallExpr.args[0], functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr as Node, contains, parent))
}
}
return noModifications
}
@@ -540,10 +636,25 @@ class ExpressionSimplifier(private val program: Program,
return expr.left
}
}
256.0 -> {
when(leftDt) {
DataType.UBYTE -> return NumericLiteral(DataType.UBYTE, 0.0, expr.position)
DataType.BYTE -> return null // is either 0 or -1 we cannot tell here
DataType.UWORD, DataType.WORD -> {
// just use: msb(value) as type
val msb = BuiltinFunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return if(leftDt==DataType.WORD)
TypecastExpression(msb, DataType.BYTE, true, expr.position)
else
TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else -> return null
}
}
in powersOfTwo -> {
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
// Unsigned number divided by a power of two => shift right
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values), TODO is this correct???
// so we leave that as is and let the code generator deal with it.
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
@@ -705,8 +816,8 @@ class ExpressionSimplifier(private val program: Program,
}
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
errors.err("useless to shift by more than 15 bits", expr.position)
return null
}
else if(amount==8) {
// shift right by 8 bits is just a byte operation: msb(X) as uword
@@ -720,11 +831,21 @@ class ExpressionSimplifier(private val program: Program,
}
}
DataType.WORD -> {
// bit-shifting a signed value shouldn't be allowed by the compiler but here we go...
if (amount > 16) {
expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
if (amount >= 16) {
errors.err("useless to shift by more than 15 bits", expr.position)
return null
}
else if(amount == 8) {
// shift right by 8 bits is just a byte operation: msb(X) as byte (will get converted to word later)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, DataType.BYTE, true, expr.position)
}
else if(amount > 8) {
// same as above but with residual shifts. Take care to do signed shift.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
val signed = TypecastExpression(msb, DataType.BYTE, true, expr.position)
return BinaryExpression(signed, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
}
}
else -> {
}
@@ -7,13 +7,13 @@ import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, options, errors)
valuetypefixer.visit(this)
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
val replacer = ConstantIdentifierReplacer(this, errors, options.compTarget)
replacer.visit(this)
if (errors.noErrors()) {
replacer.applyModifications()
@@ -22,7 +22,7 @@ fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget)
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.visit(this)
while (errors.noErrors() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
+109 -85
View File
@@ -34,85 +34,100 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
if(!containsSubsOrVariables) {
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when(val stmt=subroutine.statements[0]) {
is Return -> {
if(stmt.value is NumericLiteral)
true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if(stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size<=1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
if(subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
is Return -> {
if (stmt.value is NumericLiteral)
true
else if (stmt.value == null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if (stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size <= 1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
}
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
}
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
}
} else
false
}
is Assignment -> {
if(stmt.value.isSimple) {
val targetInline =
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if(stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else
false
}
else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
is Assignment -> {
if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
}
is Jump -> true
else -> false
}
is Jump -> true
else -> false
}
}
}
if(subroutine.inline && subroutine.statements.size>1) {
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
}
@@ -129,16 +144,20 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
private fun makeFullyScoped(call: FunctionCallStatement) {
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -146,8 +165,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -155,8 +176,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -165,7 +188,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
when (it) {
is NumericLiteral -> it.copy()
is IdentifierReference -> {
val scoped = (it.targetStatement(program)!! as INamedStatement).scopedName
val target = it.targetStatement(program) ?: return emptyList()
val scoped = (target as INamedStatement).scopedName
IdentifierReference(scoped, it.position)
}
else -> throw InternalCompilerException("expected only number or identifier arg, otherwise too complex")
@@ -30,7 +30,7 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
if(arg.arrayIndex==null) arg.identifier else null
} else {
arg as? IdentifierReference
}
@@ -45,11 +45,7 @@ class StatementOptimizer(private val program: Program,
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
val chrout1 = FunctionCallStatement(
@@ -62,11 +58,9 @@ class StatementOptimizer(private val program: Program,
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
}
@@ -77,6 +71,11 @@ class StatementOptimizer(private val program: Program,
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
errors.warn("condition is always ${constvalue.asBooleanValue}", ifElse.condition.position)
}
// remove empty if statements
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
@@ -93,17 +92,12 @@ class StatementOptimizer(private val program: Program,
)
}
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}
@@ -116,12 +110,27 @@ class StatementOptimizer(private val program: Program,
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
// switch if/else around if the else is just a jump or branch
if(ifElse.elsepart.isNotEmpty() && ifElse.elsepart.statements.size==1) {
val jump = ifElse.elsepart.statements[0]
if(jump is Jump) {
val newTruePart = AnonymousScope(mutableListOf(jump), ifElse.elsepart.position)
val newElsePart = AnonymousScope(ifElse.truepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, newElsePart, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, newTruePart, ifElse),
IAstModification.ReplaceNode(ifElse.condition, invertCondition(ifElse.condition, program), ifElse)
)
}
}
return noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position)
errors.info("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
@@ -157,7 +166,7 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
else if(iterable.datatype in ArrayDatatypes) {
else if(iterable.isArray) {
val size = iterable.arraysize!!.constIndex()
if(size==1) {
// loop over array of length 1 -> just assign the single value
@@ -174,6 +183,70 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
if (range != null && range.from.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==1.0) {
val toBinExpr = range.to as? BinaryExpression
if(toBinExpr!=null && toBinExpr.operator=="-" && toBinExpr.right.constValue(program)?.number==1.0) {
// FOR var IN 0 TO X-1 .... ---> var=0, DO {... , var++} UNTIL var==X
val pos = forLoop.position
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", toBinExpr.left, pos)
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
if(options.compTarget.name!=VMTarget.NAME) {
// this optimization is not effective for the VM target.
val toConst = range.to.constValue(program)
if (toConst == null) {
// FOR var in 0 TO X ... ---> var=0, REPEAT { ... , IF var==X break , var++ }
val pos = forLoop.position
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
val breakCondition = IfElse(
BinaryExpression(forLoop.loopVar, "==", range.to, pos),
AnonymousScope(mutableListOf(Break(pos)), pos),
AnonymousScope(mutableListOf(), pos),
pos
)
forLoop.body.statements.add(breakCondition)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
RepeatLoop(null, forLoop.body, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "--", pos)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
return noModifications
}
@@ -213,7 +286,7 @@ class StatementOptimizer(private val program: Program,
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position)
errors.info("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
val iterations = iter.constValue(program)?.number?.toInt()
@@ -384,7 +457,8 @@ class StatementOptimizer(private val program: Program,
}
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
return if(unrollLoop.iterations<1)
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
return if(iterations!=null && iterations<1)
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
else
noModifications
@@ -394,14 +468,14 @@ class StatementOptimizer(private val program: Program,
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
errors.warn("for boolean condition a normal if statement is preferred", whenStmt.position)
errors.info("for boolean condition a normal if statement is preferred", whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
if(whenStmt.condition.inferType(program).isBool) {
if(whenStmt.choices.all { it.values?.size==1 }) {
if (whenStmt.choices.all { it.values!!.single().constValue(program)!!.number in arrayOf(0.0, 1.0) }) {
// it's a when statement on booleans that can just be replaced by an if or if..else.
// it's a when statement on booleans that can just be replaced by an if or if-else.
if (whenStmt.choices.size == 1) {
return if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
replaceWithIf(whenStmt.condition, whenStmt.choices[0].statements, null)
@@ -429,6 +503,27 @@ class StatementOptimizer(private val program: Program,
}
}
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant
var matchingChoice: WhenChoice? = null
loop@ for(choice in whenStmt.choices) {
for(value in choice.values ?: emptyList()) {
if(value.constValue(program)?.number == constantValue) {
matchingChoice = choice
break@loop
}
}
}
if(matchingChoice==null)
matchingChoice = whenStmt.choices.singleOrNull { it.values==null }
if(matchingChoice!=null) {
// get rid of the whole when-statement and just leave the matching choice
return listOf(IAstModification.ReplaceNode(whenStmt, matchingChoice.statements, parent))
}
}
return noModifications
}
@@ -66,14 +66,14 @@ class UnusedCodeRemover(private val program: Program,
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.warn("removing unused block '${block.name}'", block.position)
errors.info("removing unused block '${block.name}'", block.position)
}
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR}) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.warn("removing unused block '${block.name}'", block.position)
errors.info("removing unused block '${block.name}'", block.position)
}
if(!block.statements.any { it is Subroutine && it.hasBeenInlined }) {
program.removeInternedStringsFromRemovedBlock(block)
@@ -90,8 +90,8 @@ class UnusedCodeRemover(private val program: Program,
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.isAsmSubroutine) {
if(callgraph.unused(subroutine)) {
if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
if("ignore_unused" !in subroutine.definingBlock.options())
errors.info("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
callgraph.calledBy[subroutine]?.let {
for(node in it)
@@ -99,8 +99,8 @@ class UnusedCodeRemover(private val program: Program,
}
return removals
}
if(!subroutine.definingModule.isLibrary && !subroutine.hasBeenInlined) {
errors.warn("unused subroutine '${subroutine.name}'", subroutine.position)
if(!subroutine.hasBeenInlined && "ignore_unused" !in subroutine.definingBlock.options()) {
errors.info("unused subroutine '${subroutine.name}'", subroutine.position)
}
if(!subroutine.inline) {
program.removeInternedStringsFromRemovedSubroutine(subroutine)
@@ -119,8 +119,8 @@ class UnusedCodeRemover(private val program: Program,
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm) {
val usages = callgraph.usages(decl)
if (usages.isEmpty()) {
if(!decl.definingModule.isLibrary)
errors.warn("removing unused variable '${decl.name}'", decl.position)
if("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
}
else {
@@ -131,23 +131,30 @@ class UnusedCodeRemover(private val program: Program,
if(assignment!=null && assignment.origin==AssignmentOrigin.VARINIT) {
if(assignment.value.isSimple) {
// remove the vardecl
if(!decl.definingModule.isLibrary)
errors.warn("removing unused variable '${decl.name}'", decl.position)
if("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(
IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
} else if(assignment.value is IFunctionCall) {
// replace the unused variable's initializer function call by a void
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
// but only if the vardecl immediately precedes it!
if(singleUse.parent.parent === parent) {
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
} else {
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
errors.info("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
}
}
}
+5 -5
View File
@@ -33,14 +33,14 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
configurations.all {
@@ -59,10 +59,10 @@ configurations {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
+92 -7
View File
@@ -1,20 +1,26 @@
; Prog8 definitions for the Atari800XL
; Including memory registers, I/O registers, Basic and Kernal subroutines.
%option no_symbol_prefixing, ignore_unused
atari {
%option no_symbol_prefixing
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
&uword COLCRS = 85
&ubyte ROWCRS = 84
romsub $F24A = getchar() -> ubyte @A
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2FD = waitkey() -> ubyte @A
}
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
asmsub init_system() {
@@ -198,9 +204,57 @@ _longcopy
}}
}
inline asmsub exit(ubyte returnvalue @A) {
sub disable_caseswitch() {
; no-op
}
sub enable_caseswitch() {
; no-op
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO
; TODO where to store A as exit code?
%asm {{
ldx prog8_lib.orig_stackpointer
txs
@@ -215,11 +269,37 @@ _longcopy
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
}}
}
inline asmsub pushw(uword value @AY) {
%asm {{
pha
tya
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
}}
}
inline asmsub popw() -> uword @AY {
%asm {{
pla
tay
pla
}}
}
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the Atari as well but their location in memory is different
; TODO
@@ -351,4 +431,9 @@ cx16 {
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
+36 -20
View File
@@ -1,4 +1,5 @@
; Prog8 definitions for the Text I/O and Screen routines for the Atari 800XL
; Reference: https://www.atariarchives.org/mapping/appendix12.php
%import syslib
%import conv
@@ -6,7 +7,7 @@
txt {
%option no_symbol_prefixing
%option no_symbol_prefixing, ignore_unused
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 24
@@ -24,15 +25,25 @@ sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
sub column(ubyte col) {
; ---- set the cursor on the given column (starting with 0) on the current line
; TODO
%asm {{
rts
}}
atari.COLCRS = col
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
sub get_column() -> ubyte {
return atari.COLCRS as ubyte
}
sub row(ubyte rownum) {
; ---- set the cursor on the given row (starting with 0) on the current line
atari.ROWCRS = rownum
}
sub get_row() -> ubyte {
return atari.ROWCRS
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
; TODO
@@ -42,7 +53,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
; TODO
@@ -114,10 +125,7 @@ asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
}
romsub $F2B0 = outchar(ubyte char @ A)
romsub $F2Fd = waitkey()
asmsub chrout(ubyte char @ A) {
asmsub chrout(ubyte character @ A) {
%asm {{
sta _tmp_outchar+1
txa
@@ -126,7 +134,7 @@ asmsub chrout(ubyte char @ A) {
pha
_tmp_outchar
lda #0
jsr outchar
jsr atari.outchar
pla
tay
pla
@@ -364,7 +372,7 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; TODO
%asm {{
@@ -372,12 +380,20 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) {
; ---- set cursor at specific position
; TODO
%asm {{
rts
}}
sub plot(ubyte col, ubyte rownum) {
column(col)
row(rownum)
}
sub get_cursor(uword colptr, uword rowptr) {
@(colptr) = get_column()
@(rowptr) = get_row()
}
asmsub waitkey() -> ubyte @A {
%asm {{
jmp atari.waitkey
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
-62
View File
@@ -1,62 +0,0 @@
%import syslib
%import textio
; Bitmap pixel graphics module for the Commodore 128
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
graphics {
%option no_symbol_prefixing
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
; TODO
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
; TODO
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
; TODO
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; TODO
}
sub fillrect(uword x, uword y, uword width, uword height) {
; TODO
}
sub rect(uword x, uword y, uword width, uword height) {
; TODO
}
sub horizontal_line(uword x, uword y, uword length) {
; TODO
}
sub vertical_line(uword x, uword y, uword height) {
; TODO
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
%asm {{
nop ; TODO
}}
}
}
+147 -92
View File
@@ -1,9 +1,10 @@
; Prog8 definitions for the Commodore-128
; Including memory registers, I/O registers, Basic and Kernal subroutines.
%option no_symbol_prefixing, ignore_unused
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
@@ -78,7 +79,7 @@ romsub $FFAB = UNTLK() clobbers(A) ; command serial
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 $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
@@ -87,7 +88,7 @@ romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
@@ -128,12 +129,19 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
SETNAM(0, $0000)
SETLFS(15, 3, 15)
void OPEN()
CLOSE(15)
}
}
c64 {
; C64 I/O registers (VIC, SID, CIA)
%option no_symbol_prefixing
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
@@ -288,7 +296,6 @@ c64 {
c128 {
; ---- C128 specific registers ----
%option no_symbol_prefixing
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
@@ -325,7 +332,6 @@ asmsub disable_basic() clobbers(A) {
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
@@ -335,7 +341,7 @@ asmsub init_system() {
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
; Uppercase charset is activated.
%asm {{
sei
cld
@@ -367,7 +373,13 @@ asmsub init_system_phase2() {
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
jmp sys.enable_runstop_and_charsetswitch
lda #0
sta $ff00 ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
rts
}}
}
@@ -391,77 +403,78 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers as these might be clobbered by the irq routine
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
cld
rts
_irq_handler_end
; restore all zp scratch registers
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
@@ -481,40 +494,39 @@ asmsub restore_irq() clobbers(A) {
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@@ -734,16 +746,27 @@ _longcopy
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 247
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta $ff00 ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr sys.enable_runstop_and_charsetswitch
sta 247
}}
}
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
sta cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
jmp cleanup_at_exit
}}
}
@@ -754,11 +777,37 @@ _longcopy
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
}}
}
inline asmsub pushw(uword value @AY) {
%asm {{
pha
tya
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
}}
}
inline asmsub popw() -> uword @AY {
%asm {{
pla
tay
pla
}}
}
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C128 as well but their location in memory is different
; (because there's no room for them in the zeropage)
@@ -890,4 +939,10 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
+58 -9
View File
@@ -1,11 +1,13 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
txt {
%option no_symbol_prefixing
%option no_symbol_prefixing, ignore_unused
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
@@ -38,6 +40,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@@ -238,10 +279,10 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -427,7 +468,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -466,7 +508,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
_screenrows .word cbm.Screen + range(0, 1000, 40)
}}
}
@@ -533,7 +575,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@@ -545,13 +587,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
adc col
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
+ lda character
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
@@ -584,4 +626,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+7 -3
View File
@@ -418,8 +418,9 @@ pushFAC1 .proc
rts
.pend
popFAC1 .proc
; -- pop floating point value from cpu stack into FAC1
popFAC .proc
; -- pop floating point value from cpu stack into FAC1 or FAC2 (
; carry flag clear=FAC1, carry set=FAC2
; save return address
pla
sta P8ZP_SCRATCH_W2
@@ -437,8 +438,11 @@ popFAC1 .proc
sta floats.floats_temp_var
lda #<floats.floats_temp_var
ldy #>floats.floats_temp_var
bcs +
jsr floats.MOVFM
; re-push return address
jmp ++
+ jsr floats.CONUPK
+ ; re-push return address
lda P8ZP_SCRATCH_W2+1
pha
lda P8ZP_SCRATCH_W2
+24 -8
View File
@@ -1,14 +1,13 @@
; Prog8 definitions for floating point handling on the Commodore-64
%option enable_floats
%option enable_floats, no_symbol_prefixing, ignore_unused
%import floats_functions
floats {
; ---- this block contains C-64 floating point related functions ----
%option no_symbol_prefixing
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- C64 basic and kernal ROM float constants and functions ----
@@ -46,13 +45,13 @@ romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
@@ -77,6 +76,7 @@ romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
@@ -157,7 +157,7 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
sub rndf() -> float {
sub rnd() -> float {
%asm {{
lda #1
jsr FREADSA
@@ -165,6 +165,22 @@ sub rndf() -> float {
}}
}
asmsub parse(str value @AY) -> float @FAC1 {
%asm {{
sta $22
sty $23
jsr prog8_lib.strlen
tya
jmp FREADSTR
}}
}
asmsub normalize(float value @FAC1) -> float @ FAC1 {
%asm {{
jmp floats.NORMAL
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
@@ -147,3 +147,54 @@ func_sqrt_into_FAC1 .proc
jsr MOVFM
jmp SQR
.pend
containment_floatarray .proc
; -- check if a value exists in a float array.
; parameters: FAC1: value to check, P8ZP_SCRATCH_W1: address of the word array, Y = length of array (>=1).
; returns boolean 0/1 in A.
sty P8ZP_SCRATCH_REG
ldx #<floats.floats_temp_var
ldy #>floats.floats_temp_var
jsr floats.MOVMF
ldx P8ZP_SCRATCH_REG
ldy #0
- lda floats.floats_temp_var
cmp (P8ZP_SCRATCH_W1),y
bne _firstmiss
iny
lda floats.floats_temp_var+1
cmp (P8ZP_SCRATCH_W1),y
bne _secondmiss
iny
lda floats.floats_temp_var+2
cmp (P8ZP_SCRATCH_W1),y
bne _thirdmiss
iny
lda floats.floats_temp_var+3
cmp (P8ZP_SCRATCH_W1),y
bne _fourthmiss
iny
lda floats.floats_temp_var+4
cmp (P8ZP_SCRATCH_W1),y
bne _fifthmiss
lda #1
rts
_firstmiss
iny
_secondmiss
iny
_thirdmiss
iny
_fourthmiss
iny
_fifthmiss
iny
dex
bne -
lda #0
rts
.pend
+82 -69
View File
@@ -1,35 +1,46 @@
%import textio
; bitmap pixel graphics module for the C64
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
; so that the program itself can be larger without starting to overwrite the graphics memory.
graphics {
%option no_symbol_prefixing
%option ignore_unused
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
const uword BITMAP_ADDRESS = $6000 ; MUST BE IN REGULAR RAM if you are not messing with ROM/RAM banking. (and $2000-aligned)
; note: this constant is also used in a asm multiplication table below!
const uword CHARS_ADDRESS = $5c00 ; must be in same vic memory bank as the bitmap.
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
c64.SCROLY = %00111011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
clear_screen(1, 0)
c64.SCROLY = %00111011 ; enable bitmap graphics mode
c64.SCROLX = %00001000 ; 40 column mode, no scrolling, multicolor bitmap off
c64.VMCSB = (lsb(CHARS_ADDRESS >> 6) & $F0) | (lsb((BITMAP_ADDRESS & $3fff) / $0800) << 1) ; set bitmap address
c64.CIA2DDRA |= %11
c64.CIA2PRA = lsb(BITMAP_ADDRESS >> 14) ^ 3 ; set VIC bank.
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
c64.SCROLY = %00011011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
txt.fill_screen(' ', 1)
; enables erase the text screen, text mode
sys.memset(CHARS_ADDRESS, 40*25, sc:' ')
c64.SCROLY = %00011011 ; disable bitmap graphics mode
c64.SCROLX = %00001000 ; 40 column mode, no scrolling
c64.VMCSB = %00010100 ; screen addresses back to defaults
c64.CIA2DDRA |= %11
c64.CIA2PRA = %11 ; back to VIC bank 0.
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
sys.memset(CHARS_ADDRESS, 40*25, pixelcolor << 4 | bgcolor)
sys.memset(cbm.Colors, 40*25, 255)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
@@ -127,78 +138,78 @@ graphics {
}
}
sub rect(uword x, ubyte y, uword width, ubyte height) {
sub rect(uword xx, ubyte yy, uword width, ubyte height) {
if width==0 or height==0
return
horizontal_line(x, y, width)
horizontal_line(xx, yy, width)
if height==1
return
horizontal_line(x, y+height-1, width)
vertical_line(x, y+1, height-2)
horizontal_line(xx, yy+height-1, width)
vertical_line(xx, yy+1, height-2)
if width==1
return
vertical_line(x+width-1, y+1, height-2)
vertical_line(xx+width-1, yy+1, height-2)
}
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
sub fillrect(uword xx, ubyte yy, uword width, ubyte height) {
if width==0
return
repeat height {
horizontal_line(x, y, width)
y++
horizontal_line(xx, yy, width)
yy++
}
}
sub horizontal_line(uword x, ubyte y, uword length) {
sub horizontal_line(uword xx, ubyte yy, uword length) {
if length<8 {
internal_plotx=x
internal_plotx=xx
repeat lsb(length) {
internal_plot(y)
internal_plot(yy)
internal_plotx++
}
return
}
ubyte separate_pixels = lsb(x) & 7
uword addr = get_y_lookup(y) + (x&$fff8)
ubyte separate_pixels = lsb(xx) & 7
uword pixaddr = get_y_lookup(yy) + (xx&$fff8)
if separate_pixels {
%asm {{
lda addr
lda p8v_pixaddr
sta P8ZP_SCRATCH_W1
lda addr+1
lda p8v_pixaddr+1
sta P8ZP_SCRATCH_W1+1
ldy separate_pixels
ldy p8v_separate_pixels
lda hline_filled_right,y
eor #255
ldy #0
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
}}
addr += 8
pixaddr += 8
length += separate_pixels
length -= 8
}
if length {
%asm {{
lda length
lda p8v_length
and #7
sta separate_pixels
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda addr
sta p8v_separate_pixels
lsr p8v_length+1
ror p8v_length
lsr p8v_length+1
ror p8v_length
lsr p8v_length+1
ror p8v_length
lda p8v_pixaddr
sta _modified+1
lda addr+1
lda p8v_pixaddr+1
sta _modified+2
lda length
ora length+1
lda p8v_length
ora p8v_length+1
beq _zero
ldy length
ldy p8v_length
ldx #$ff
_modified stx $ffff ; modified
lda _modified+1
@@ -210,7 +221,7 @@ _modified stx $ffff ; modified
+ dey
bne _modified
_zero
ldy separate_pixels
ldy p8v_separate_pixels
beq hline_zero2
lda _modified+1
sta P8ZP_SCRATCH_W1
@@ -227,16 +238,17 @@ hline_zero2
}
}
sub vertical_line(uword x, ubyte y, ubyte height) {
internal_plotx = x
sub vertical_line(uword xx, ubyte yy, ubyte height) {
internal_plotx = xx
repeat height {
internal_plot(y)
y++
internal_plot(yy)
yy++
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
ubyte @zp ploty
@@ -265,16 +277,17 @@ hline_zero2
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
@@ -287,12 +300,12 @@ hline_zero2
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
@@ -300,15 +313,15 @@ hline_zero2
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; uword pixaddr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(pixaddr) |= ormask[lsb(px) & 7]
; }
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{
stx graphics.internal_plotx
sty graphics.internal_plotx+1
jsr graphics.internal_plot
stx p8b_graphics.p8v_internal_plotx
sty p8b_graphics.p8v_internal_plotx+1
jsr p8b_graphics.p8s_internal_plot
}}
}
@@ -320,11 +333,11 @@ hline_zero2
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
lda internal_plotx+1
lda p8v_internal_plotx+1
sta P8ZP_SCRATCH_W2+1
lsr a ; 0
sta P8ZP_SCRATCH_W2
lda internal_plotx
lda p8v_internal_plotx
pha
and #7
tax
@@ -352,7 +365,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
_plot_y_values := p8c_BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
@@ -360,11 +373,11 @@ _y_lookup_hi .byte >_plot_y_values
}}
}
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
asmsub get_y_lookup(ubyte yy @Y) -> uword @AY {
%asm {{
lda internal_plot._y_lookup_lo,y
lda p8s_internal_plot._y_lookup_lo,y
pha
lda internal_plot._y_lookup_hi,y
lda p8s_internal_plot._y_lookup_hi,y
tay
pla
rts
+183 -101
View File
@@ -1,13 +1,11 @@
; Prog8 definitions for the Commodore-64
; Including memory registers, I/O registers, Basic and Kernal subroutines.
%option no_symbol_prefixing, ignore_unused
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
&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
@@ -82,7 +80,7 @@ romsub $FFAB = UNTLK() clobbers(A) ; command serial
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 $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
@@ -91,7 +89,7 @@ romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
@@ -128,14 +126,23 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
SETNAM(0, $0000)
SETLFS(15, 3, 15)
void OPEN()
CLOSE(15)
}
}
c64 {
; C64 I/O registers (VIC, SID, CIA)
%option no_symbol_prefixing
; the default locations of the 8 sprite pointers (store address of sprite / 64)
; (depending on the VIC bank and screen ram address selection these can be shifted around though,
; see the two routines after this for a dynamic way of determining the correct memory location)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
@@ -284,13 +291,44 @@ c64 {
; ---- end of SID registers ----
sub get_vic_memory_base() -> uword {
; one of the 4 possible banks. $0000/$4000/$8000/$c000.
c64.CIA2DDRA |= %11
return ((c64.CIA2PRA & 3) ^ 3) as uword << 14
}
sub get_char_matrix_ptr() -> uword {
; Usually the character screen matrix is at 1024-2039 (see above)
; However the vic memory configuration can be altered and this moves these registers with it.
; So this routine determines it dynamically from the VIC memory setup.
uword chars_matrix_offset = (c64.VMCSB & $f0) as uword << 6
return get_vic_memory_base() + chars_matrix_offset
}
sub get_bitmap_ptr() -> uword {
return get_vic_memory_base() + ((c64.VMCSB & %00001000) as uword << 10)
}
sub get_sprite_addr_ptrs() -> uword {
; Usually the sprite address pointers are at addresses 2040-2047 (see above)
; However the vic memory configuration can be altered and this moves these registers with it.
; So this routine determines it dynamically from the VIC memory setup.
return get_char_matrix_ptr() + 1016
}
sub set_sprite_ptr(ubyte sprite_num, uword sprite_data_address) {
; Sets the sprite data pointer to the given address.
; Because it takes some time to calculate things based on the vic memory setup,
; its only suitable if you're not continuously changing the data address.
; Otherwise store the correct sprite data pointer location somewhere yourself and reuse it.
@(get_sprite_addr_ptrs() + sprite_num) = lsb(sprite_data_address / 64)
}
}
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
@@ -300,7 +338,7 @@ asmsub init_system() {
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
; Uppercase charset is activated.
%asm {{
sei
cld
@@ -334,7 +372,13 @@ asmsub init_system_phase2() {
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
jmp sys.enable_runstop_and_charsetswitch
lda #31
sta $01 ; bank the kernal in
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
rts
}}
}
@@ -358,78 +402,77 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers as these might be clobbered by the irq routine
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
cld
rts
_irq_handler_end
; restore all zp scratch registers
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}}
}
asmsub restore_irq() clobbers(A) {
@@ -448,40 +491,39 @@ asmsub restore_irq() clobbers(A) {
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@@ -702,16 +744,27 @@ _longcopy
}}
}
inline asmsub exit(ubyte returnvalue @A) {
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 657
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta 657
}}
}
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
lda #31
sta $01 ; bank the kernal in
jsr cbm.CLRCHN ; reset i/o channels
jsr sys.enable_runstop_and_charsetswitch
sta cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
jmp cleanup_at_exit
}}
}
@@ -722,12 +775,36 @@ _longcopy
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
}}
}
inline asmsub pushw(uword value @AY) {
%asm {{
pha
tya
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
}}
}
inline asmsub popw() -> uword @AY {
%asm {{
pla
tay
pla
}}
}
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C64 as well but their location in memory is different
; (because there's no room for them in the zeropage in the default configuration)
@@ -861,4 +938,9 @@ cx16 {
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
+59 -10
View File
@@ -1,4 +1,6 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
@@ -6,7 +8,7 @@
txt {
%option no_symbol_prefixing
%option no_symbol_prefixing, ignore_unused
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
@@ -39,7 +41,46 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@@ -54,7 +95,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
@@ -237,10 +278,10 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PESCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -426,7 +467,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -465,7 +507,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
_screenrows .word cbm.Screen + range(0, 1000, 40)
}}
}
@@ -532,7 +574,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@@ -544,13 +586,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
adc col
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
+ lda character
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
@@ -583,4 +625,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
conv {
%option no_symbol_prefixing
%option no_symbol_prefixing, ignore_unused
; ----- number conversions to decimal strings ----
+362
View File
@@ -0,0 +1,362 @@
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
; Only uncompressed images are supported for now.
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
%import diskio
%option ignore_unused
bmx {
ubyte[32] header
str FILEID = petscii:"bmx"
ubyte bitsperpixel ; consider using set_bpp() when setting this
ubyte vera_colordepth ; consider using set_vera_colordepth() when setting this
uword width
uword height
ubyte border
uword palette_entries ; 1-256
ubyte palette_start
ubyte compression
uword @shared palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
uword error_message ; pointer to error message, or 0 if all ok
ubyte old_drivenumber
sub open(ubyte drivenumber, str filename) -> bool {
; Open a BMX bitmap file and reads the header information.
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open(filename) {
diskio.reset_read_channel()
if read_header() {
if parse_header() {
if palette_entries>0 {
if width<=640 {
return true
} else
error_message = "image too large"
} else
error_message = "invalid bmx file"
} ; note: parse_header sets the error message by itself
} else
error_message = "invalid bmx file"
} else
error_message = diskio.status()
close()
return false
}
sub close() {
; if you want to close the file before actually loading palette or bitmap data.
diskio.f_close()
diskio.drivenumber = old_drivenumber
}
sub continue_load(ubyte vbank, uword vaddr) -> bool {
; Continues loading the palette and bitmap data from the opened BMX file.
; Parameters: the vram bank and address where the bitmap data should go.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
diskio.reset_read_channel()
if width==320 or width==640 {
if compression==0 {
if read_palette() {
if not read_bitmap(vbank, vaddr)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "compression not supported"
} else
error_message = "width not 320 or 640" ; note: use continue_load_stamp() to read other sizes
close()
return error_message==0
}
sub continue_load_stamp(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; Continues loading the palette and bitmap "stamp" data from the opened BMX file.
; "Stamp" means: load an image that is smaller than the screen (so we need to pad around it)
; Parameters:the vram bank and address where the bitmap data should go,
; and the screen width that the stamp image is loaded into.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
diskio.reset_read_channel()
if compression==0 {
if read_palette() {
if not read_bitmap_padded(vbank, vaddr, screenwidth)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "compression not supported"
close()
return error_message==0
}
sub continue_load_only_palette() -> bool {
; Continues loading the palette but not the bitmap data from the opened BMX file.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Returns true if all is ok, false otherwise + error_message will be set.
; Afterwards the file is closed and you can no longer read additional data from it!
error_message = 0
diskio.reset_read_channel()
if not read_palette()
error_message = "palette error"
close()
return error_message==0
}
sub save(ubyte drivenumber, str filename, ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; Save bitmap and palette data from vram into a BMX file.
; First you must have set all bmx.* variables to the correct values! (like width, height..)
; Parameters:
; drive number and filename to save to,
; vram bank and address of the bitmap data to save,
; and optionally the screen width if you want to save a "stamp" that's smaller than the screen.
; If you're saving the whole screen width, you can leave screenwidth at 0.
; Returns: success status. If false, error_message points to the error message string.
error_message = 0
if compression {
error_message = "compression not supported"
return false
}
if screenwidth==0 {
if width!=320 and width!=640 {
error_message = "width not 320 or 640"
return false
}
} else {
if width>screenwidth {
error_message = "image too large"
return false
}
}
old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open_w(filename) {
cx16.r0 = diskio.status()
if cx16.r0[0]!='0' {
error_message = cx16.r0
goto save_end
}
diskio.reset_write_channel()
if write_header() {
if write_palette() {
if not write_bitmap(vbank, vaddr, screenwidth)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "header error"
} else
error_message = diskio.status()
save_end:
diskio.f_close_w()
diskio.drivenumber = old_drivenumber
return error_message==0
}
sub set_bpp(ubyte bpp) {
ubyte[8] depths = [0,1,1,2,2,2,2,3]
vera_colordepth = depths[bpp-1]
}
sub set_vera_colordepth(ubyte depth) {
vera_colordepth = depth
bitsperpixel = 1 << depth
}
; ------------------- helper routines, usually not needed to call yourself -------------------------
sub read_header() -> bool {
; load a BMX header from the currently active input file
for cx16.r0L in 0 to sizeof(header)-1 {
header[cx16.r0L] = cbm.CHRIN()
}
return not cbm.READST()
}
sub read_palette() -> bool {
; load palette data from the currently active input file
; if palette_buffer_ptr is not 0, the palette data is read into that memory buffer,
; otherwise it is read directly into the palette in vram.
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
cx16.r3 = palette_buffer_ptr
cx16.r2L = lsb(palette_entries)
do {
cx16.r4L = cbm.CHRIN()
cx16.r4H = cbm.CHRIN()
if cx16.r3 {
pokew(cx16.r3, cx16.r4) ; into memory
cx16.r3+=2
} else {
cx16.VERA_DATA0 = cx16.r4L ; into vram
cx16.VERA_DATA0 = cx16.r4H
}
cx16.r2L--
} until cx16.r2L==0
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_bitmap_padded(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; load bitmap "stamp" into vram from the currently active input file
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
cx16.r2 = bytes_per_scanline(screenwidth) ; num bytes per screen scanline
repeat height {
cx16.vaddr(vbank, vaddr, 0, 1)
read_scanline(cx16.r3)
vaddr += cx16.r2
if_cs
vbank++
}
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_bitmap(ubyte vbank, uword vaddr) -> bool {
; load contiguous bitmap into vram from the currently active input file
cx16.r3 = bytes_per_scanline(width)
cx16.vaddr(vbank, vaddr, 0, 1)
repeat height
read_scanline(cx16.r3)
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_scanline(uword size) {
while size {
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
if_cs {
; no MACPTR support
repeat size
cx16.VERA_DATA0 = cbm.CHRIN()
return
}
size -= cx16.r0
}
}
sub write_header() -> bool {
; save out the BMX header to the currently active output file
build_header()
for cx16.r0L in 0 to sizeof(header)-1 {
cbm.CHROUT(header[cx16.r0L])
}
return not cbm.READST()
}
sub write_palette() -> bool {
; write palette data to the currently active input file
; if palette_buffer_ptr is not 0, the palette data is read from that memory buffer,
; otherwise it is read directly from the palette in vram.
cx16.r3 = palette_buffer_ptr
cx16.r2L = lsb(palette_entries)
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
do {
if cx16.r3 {
cbm.CHROUT(@(cx16.r3)) ; from memory
cx16.r3++
cbm.CHROUT(@(cx16.r3))
cx16.r3++
} else {
cbm.CHROUT(cx16.VERA_DATA0) ; from vram
cbm.CHROUT(cx16.VERA_DATA0)
}
cx16.r2L--
} until cx16.r2L==0
return not cbm.READST()
}
sub write_bitmap(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; screenwidth=0: save contiguous bitmap from vram to the currently active output file
; screenwidth>0: save "stamp" bitmap from vram to the currently active output file
cx16.vaddr(vbank, vaddr, 0, 1)
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
cx16.r2 = 0
if screenwidth
cx16.r2 = bytes_per_scanline(screenwidth-width) ; num bytes padding per screen scanline
repeat height {
write_scanline(cx16.r3)
repeat cx16.r2
cx16.r0L = cx16.VERA_DATA0
}
return not cbm.READST()
sub write_scanline(uword size) {
while size {
cx16.r0L = lsb(size)
if msb(size)
cx16.r0L = 0 ; 256 bytes
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
if_cs {
; no MCIOUT support
repeat size
cbm.CHROUT(cx16.VERA_DATA0)
return
}
size -= cx16.r0
}
}
}
sub bytes_per_scanline(uword w) -> uword {
ubyte[4] shifts = [3,2,1,0]
return w >> shifts[vera_colordepth]
}
sub parse_header() -> bool {
if header[0]==FILEID[0] and header[1]==FILEID[1] and header[2]==FILEID[2] {
if header[3]==1 { ; only version 1 supported for now
bitsperpixel = header[4]
vera_colordepth = header[5]
width = peekw(&header+6)
height = peekw(&header+8)
palette_entries = header[10]
if palette_entries==0
palette_entries=256
palette_start = header[11]
; the data offset is not needed: data_offset = peekw(&header+12)
compression = header[14]
border = header[15]
return true
} else
error_message = "unsupported bmx file version"
} else
error_message = "invalid bmx file"
return false
}
sub build_header() {
; build the internal BMX header structure
; normally you don't have to call this yourself
sys.memset(header, sizeof(header), 0)
uword data_offset = palette_entries*$0002
data_offset += sizeof(header)
header[0] = FILEID[0]
header[1] = FILEID[1]
header[2] = FILEID[2]
header[3] = 1 ; version 1
header[4] = bitsperpixel
header[5] = vera_colordepth
header[6] = lsb(width)
header[7] = msb(width)
header[8] = lsb(height)
header[9] = msb(height)
header[10] = lsb(palette_entries)
header[11] = palette_start
header[12] = lsb(data_offset)
header[13] = msb(data_offset)
header[14] = compression
header[15] = border
}
}
+162 -107
View File
@@ -1,31 +1,47 @@
; Commander X16 disk drive I/O routines.
; Largely compatible with the default C64 ones, but adds more stuff specific to the X16 as well.
; NOTE: If you experience weird behavior with these routines and you are using them
; in the X16 emulator using HostFs, please try again with an SD-card image instead first.
; It is possible that there are still small differences between HostFS and actual CBM DOS in the emulator.
;
; About the secondary addresses:
; for writes (new files or overwrites), you can use 1 (without the mode string) or 2-14 (with the mode string)
; for reads (existing files) you can use 0 or 2-14 (mode string is optional)
; for modify mode (new files* or existing files), you must use 2-14, and the mode string ,s,m is required
%import textio
%import conv
%import string
%import syslib
diskio {
%option no_symbol_prefixing
%option no_symbol_prefixing, ignore_unused
ubyte drivenumber = 8
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
sub set_drive(ubyte number) {
drivenumber = number
ubyte @shared drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
reset_read_channel()
repeat 4 {
void cbm.CHRIN() ; skip the 4 prologue bytes
@@ -43,12 +59,12 @@ diskio {
ubyte high = cbm.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp char
ubyte @zp character
repeat {
char = cbm.CHRIN()
if char==0
character = cbm.CHRIN()
if character==0
break
txt.chrout(char)
txt.chrout(character)
}
txt.nl()
void cbm.CHRIN() ; skip 2 bytes
@@ -61,7 +77,7 @@ diskio {
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@@ -76,25 +92,23 @@ io_error:
sub diskname() -> uword {
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
reset_read_channel()
while cbm.CHRIN()!='"' {
; skip up to entry name
}
cx16.r0 = &diskio.list_filename
cx16.r0 = &list_filename
repeat {
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
@(cx16.r0) = 0
cx16.r0--
}
@@ -106,10 +120,10 @@ io_error:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
return diskio.list_filename
return list_filename
}
; internal variables for the iterative file lister / loader
@@ -124,12 +138,13 @@ io_error:
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
uword buffer_start = filenames_buffer
ubyte files_found = 0
filenames_buffer[0]=0
if lf_start_list(pattern_ptr) {
while lf_next_entry() {
if list_filetype!="dir" {
@@ -150,7 +165,7 @@ io_error:
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
@@ -161,13 +176,11 @@ io_error:
iteration_in_progress = true
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
reset_read_channel()
repeat 4 {
void cbm.CHRIN() ; skip the 4 prologue bytes
@@ -177,6 +190,7 @@ io_error:
return true
io_error:
cbm.CLOSE(READ_IO_CHANNEL)
lf_end_list()
return false
}
@@ -190,7 +204,7 @@ io_error:
return false
repeat {
void cbm.CHKIN(12) ; use #12 as input channel again
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
@@ -209,12 +223,12 @@ io_error:
; read the filename
repeat {
ubyte char = cbm.CHRIN()
if char==0
ubyte character = cbm.CHRIN()
if character==0
break
if char=='\"'
if character=='\"'
break
@(nameptr) = char
@(nameptr) = character
nameptr++
}
@@ -251,38 +265,41 @@ close_end:
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 12) -----
; ----- iterative file loader functions (uses the read io channel) -----
sub f_open(uword filenameptr) -> bool {
sub f_open(str filenameptr) -> bool {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
; No need to check status(), unlike f_open_w() !
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
; if you're going to read from it yourself instead of using f_read()!
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
void cbm.OPEN() ; open 12,8,12,"filename"
if_cc {
reset_read_channel()
if cbm.READST()==0 {
iteration_in_progress = true
void cbm.CHKIN(12) ; use #12 as input channel
if_cc {
void cbm.CHRIN() ; read first byte to test for file not found
if not cbm.READST() {
cbm.CLOSE(12) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
void cbm.CHKIN(12)
return true
}
void cbm.CHRIN() ; read first byte to test for file not found
if cbm.READST()==0 {
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
cbm.CLRCHN() ; reset default i/o channels
return true
}
}
}
f_close()
cbm.CLOSE(READ_IO_CHANNEL)
return false
}
@@ -290,26 +307,26 @@ close_end:
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress or not num_bytes
return 0
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
; commander X16 supports fast block-read via macptr() kernal call
uword size
uword readsize
while num_bytes {
size = 255
if num_bytes<size
size = num_bytes
size = cx16.macptr(lsb(size), bufferpointer, false)
readsize = 255
if num_bytes<readsize
readsize = num_bytes
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop
list_blocks += size
bufferpointer += size
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
bufferpointer += readsize
if msb(bufferpointer) == $c0
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
num_bytes -= size
num_bytes -= readsize
if cbm.READST() & $40 {
f_close() ; end of file, close it
break
@@ -317,7 +334,7 @@ close_end:
}
return list_blocks ; number of bytes read
byte_read_loop: ; fallback if macptr() isn't supported on the device
byte_read_loop: ; fallback if MACPTR isn't supported on the device
%asm {{
lda bufferpointer
sta m_in_buffer+1
@@ -348,10 +365,11 @@ m_in_buffer sta $ffff
; optimized for Commander X16 to use MACPTR block read kernal call
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress
return 0
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
cx16.r0 = f_read(bufferpointer, 256)
@@ -370,8 +388,7 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #12
jsr cbm.CHKIN ; use channel 12 again for input
jsr reset_read_channel
ldy #0
_loop jsr cbm.CHRIN
sta (P8ZP_SCRATCH_W1),y
@@ -392,25 +409,44 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file writing functions (uses io channel 13) -----
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
sub f_open_w(str filename) -> bool {
; -- Open a file for iterative writing with f_write
; WARNING: returns true if the open command was received by the device,
; but this can still mean the file wasn't successfully opened for writing!
; (for example, if it already exists). This is different than f_open()!
; To be 100% sure if this call was successful, you have to use status()
; and check the drive's status message!
return internal_f_open_w(filename, false)
}
sub f_open_w_seek(str filename) -> bool {
; -- Open an existing file for iterative writing with f_write, and seeking with f_seek_w.
return internal_f_open_w(filename, true)
}
sub internal_f_open_w(str filename, bool open_for_seeks) -> bool {
f_close_w()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(13, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
list_filename = filename
str modifier = ",s,?"
modifier[3] = 'w'
if open_for_seeks
modifier[3] = 'm'
cx16.r0L = string.append(list_filename, modifier) ; secondary 13 requires a mode suffix to signal we're writing/modifying
cbm.SETNAM(cx16.r0L, list_filename)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
void cbm.OPEN() ; open 13,8,13,"filename"
if_cc {
cbm.CHKOUT(13) ; use #13 as output channel
return not cbm.READST()
}
cbm.CLOSE(WRITE_IO_CHANNEL)
f_close_w()
return false
}
@@ -418,7 +454,22 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
cbm.CHKOUT(13) ; use #13 as output channel again
reset_write_channel()
do {
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
if_cs
goto no_mciout
num_bytes -= cx16.r0
bufferpointer += cx16.r0
if msb(bufferpointer) == $c0
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
if cbm.READST()!=0
return false
} until num_bytes==0
return not cbm.READST()
no_mciout:
; the device doesn't support MCIOUT, use a normal per-byte write loop
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -431,7 +482,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(13)
cbm.CLOSE(WRITE_IO_CHANNEL)
}
@@ -439,6 +490,14 @@ _end rts
sub status() -> uword {
; -- retrieve the disk drive's current status message
str device_not_present_error = "device not present #xx"
if cbm.READST()==128 {
device_not_present_error[len(device_not_present_error)-2] = 0
conv.str_ub(drivenumber)
void string.copy(conv.string_out, &device_not_present_error+len(device_not_present_error)-2)
return device_not_present_error
}
uword messageptr = &list_filename
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
@@ -446,8 +505,6 @@ _end rts
if_cs
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
if_cs
goto io_error
while not cbm.READST() {
cx16.r5L = cbm.CHRIN()
@@ -464,31 +521,32 @@ done:
return list_filename
io_error:
list_filename = "?disk error"
cbm.CLOSE(15)
list_filename = "io error"
goto done
}
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(uword filenameptr, uword address, uword size) -> bool {
return internal_save_routine(filenameptr, address, size, false)
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, false)
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword address, uword size) -> bool {
return internal_save_routine(filenameptr, address, size, true)
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, true)
}
sub internal_save_routine(uword filenameptr, uword address, uword size, bool headerless) -> bool {
sub internal_save_routine(uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size
uword @shared end_address = startaddress + savesize
cx16.r0L = 0
%asm {{
lda address
lda startaddress
sta P8ZP_SCRATCH_W1
lda address+1
lda startaddress+1
sta P8ZP_SCRATCH_W1+1
ldx end_address
ldy end_address+1
@@ -506,9 +564,6 @@ io_error:
if_cc
cx16.r0L = cbm.READST()==0
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r0L
}
@@ -532,8 +587,8 @@ io_error:
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(uword filenameptr, uword address) -> uword {
return internal_load_routine(filenameptr, address, true)
sub load_raw(uword filenameptr, uword startaddress) -> uword {
return internal_load_routine(filenameptr, startaddress, true)
}
@@ -557,8 +612,6 @@ io_error:
+
}}
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r1
}
@@ -606,7 +659,7 @@ io_error:
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
asmsub vload(str name @R0, ubyte bank @A, uword address @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
; -- like the basic command VLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
@@ -645,7 +698,7 @@ internal_vload:
}}
}
asmsub vload_raw(str name @R0, ubyte bank @A, uword address @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload_raw(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
@@ -693,13 +746,11 @@ internal_vload:
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
@(cx16.r12)=0
cbm.SETNAM(3, "$=c")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
reset_read_channel()
repeat 6 {
void cbm.CHRIN()
@@ -734,7 +785,7 @@ internal_vload:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
if @(cx16.r12)==0 {
@@ -767,7 +818,7 @@ io_error:
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = 12 ; f_open uses channel 12
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
@@ -777,18 +828,22 @@ io_error:
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
}
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
; diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
; diskio.f_seek.command[2] = lsb(pos_loword)
; diskio.f_seek.command[3] = msb(pos_loword)
; diskio.f_seek.command[4] = lsb(pos_hiword)
; diskio.f_seek.command[5] = msb(pos_hiword)
; goto diskio.f_seek.send_command
; }
sub f_seek_w(uword pos_hiword, uword pos_loword) {
; -- seek in the output file opened with f_open_w_seek, to the given 32-bits position
diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses this secondary address
diskio.f_seek.command[2] = lsb(pos_loword)
diskio.f_seek.command[3] = msb(pos_loword)
diskio.f_seek.command[4] = lsb(pos_hiword)
diskio.f_seek.command[5] = msb(pos_hiword)
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLOSE(15)
reset_write_channel() ; back to the write io channel
}
}
Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff. Show more