Compare commits

...
370 Commits
Author SHA1 Message Date
Irmen de Jong d1383813d2 version 12.1.1 2026-02-13 22:11:29 +01:00
Irmen de Jong 3bd2064c3a mention empty file issue 2026-02-12 22:10:53 +01:00
Irmen de Jong f95f568319 fix mflpt5 overflow error message 2026-02-12 02:29:10 +01:00
Irmen de Jong 5eb873b722 conv.str2long() added. alias problems detected. 2026-02-12 00:38:42 +01:00
Irmen de Jong 3f181b0ede allow alias to blocks 2026-02-10 22:51:05 +01:00
Irmen de Jong d6c8fc0c61 alias refactorings 2026-02-10 21:08:35 +01:00
Irmen de Jong 709ced1bca alias test program, replace alias chains 2026-02-10 21:08:35 +01:00
Irmen de Jong 3b16d08246 arrays can contain pointers too 2026-02-10 21:03:07 +01:00
Irmen de Jong b9a6deea25 implement ror / rol on ptr expressions 2026-02-08 21:20:13 +01:00
Andy De George 099514a2c0 Fix error output file path: Encode to URI (#214) 2026-02-06 22:09:58 +01:00
Irmen de Jong ed02b84f7d micro optimization for containment checks in compiler 2026-02-06 02:43:12 +01:00
Irmen de Jong 7d6e67e251 fix numeric type error when comparing undefined symbols 2026-02-04 23:55:08 +01:00
Irmen de Jong 4aa364925d todo 2026-02-02 23:02:21 +01:00
Andrew Gillham e5ae9ad4e0 Add PET 80 column support. (#211) 2026-02-02 23:02:01 +01:00
Irmen de Jong 6329388a4e skeleton 2026-01-31 19:39:43 +01:00
Irmen de Jong ac851a35bc implement FREADUY on pet, so that ubyte to float now works 2026-01-31 19:31:26 +01:00
Irmen de Jong bc0d6453ae revert 2026-01-30 23:40:45 +01:00
Irmen de Jong 8c65e0a537 attempt to create index in latex pdf 2026-01-30 23:38:26 +01:00
Irmen de Jong d415cf356f attempt to create index in latex pdf 2026-01-30 23:33:54 +01:00
Andrew Gillham 2bd74b8c44 Add an index and set some non-ASCII fonts to improve the resulting pdf. (#210)
* Add an index and set some non-ASCII fonts to improve the resulting pdf.

* Fix coroutines index interfering with code block

* Add genindex which is needed to get a link to the html index.
2026-01-30 22:03:40 +01:00
Irmen de Jong 88b48f58c9 fix string init error about string multiplication. fixes #209 2026-01-30 17:36:48 +01:00
Irmen de Jong 75accf4059 release 12.1 2026-01-28 23:45:42 +01:00
Irmen de Jong c3f9959891 fixed invalid replacement in optimization of if-statement.
better replacement errors.
fixes #208
2026-01-28 22:58:53 +01:00
Irmen de Jong ee492a1e9a some more optimized swap() code paths 2026-01-28 00:07:10 +01:00
Irmen de Jong 415f0fb071 pet: added bunch more diskio routines like directory(), delete(), rename() and list_filenames() 2026-01-27 01:07:08 +01:00
Irmen de Jong 8e7bf4227b pet: added diskio.save() and save_raw() 2026-01-26 22:32:46 +01:00
Irmen de Jong d2b743c296 pet: added diskio.load() and load_raw() 2026-01-26 02:38:40 +01:00
Irmen de Jong cb44e76626 swap 2026-01-25 19:14:05 +01:00
Irmen de Jong b2983e912d swap longs now uses R14+R15 as temporary storage instead of R0+R1 2026-01-25 17:02:47 +01:00
Irmen de Jong 10ae42ed59 suggest import for undefined block symbols, update github links 2026-01-25 05:45:33 +01:00
Irmen de Jong 5cbd4aafcc strings: added next_token() which mimicks C's strtok() routine 2026-01-25 00:09:52 +01:00
Irmen de Jong 4aa326d5ca diskio: added lf_start_list_having_prefix()
strings: added split()
2026-01-24 22:42:54 +01:00
Irmen de Jong 39b1bbcf58 pet32: added more missing routines in textio like column(), row() 2026-01-24 19:34:33 +01:00
Irmen de Jong 4661d3bada implement simple array element swap() 2026-01-24 18:41:25 +01:00
Irmen de Jong ab98f4a5e2 fix charset encoding error with merge. Fixes #207 2026-01-24 16:13:11 +01:00
Irmen de Jong b99b054a09 cx16: add missing extapi16 call numbers
tweak swap(long,long) and swap(ptr^^, ptr^^)
2026-01-24 11:51:42 +01:00
Irmen de Jong 5d5ad8a70e fix void/nonvoid assign target issue 2026-01-24 01:11:30 +01:00
Irmen de Jong 9a5edeebbc upgrade to kotlin 2.3.0 2026-01-24 00:29:45 +01:00
Irmen de Jong 3fb45a611a make math.gcd() use swap() builtin 2026-01-24 00:14:31 +01:00
Irmen de Jong 2e0db6f211 add swap() builtin for optimized value swaps without the need for a temporary variable 2026-01-23 23:17:10 +01:00
Irmen de Jong 51ac096a31 add a -warnimplicitcasts compiler option 2026-01-23 03:06:53 +01:00
Irmen de Jong dfad83650d add math.gcd() 2026-01-23 00:03:13 +01:00
Irmen de Jong 492cd686a0 avoid invalid pointer arithmetic in joined expresssions. Fixes #206 2026-01-22 22:25:48 +01:00
Irmen de Jong 561e05a764 decent error message for unsupported struct instance expressions. Fixes #205 2026-01-22 02:47:44 +01:00
Irmen de Jong f56037142b added petgfx module for pet32, c64, c128 for petscii block characters drawing (double resolution) 2026-01-22 02:17:04 +01:00
Irmen de Jong a41cb60328 pet: working boingball example, implement floats.GIVUAYFAY
cx16: improved floats.GIVUAYFAY to no longer depend on magic zp locations
2026-01-20 22:58:47 +01:00
Irmen de Jong ca3e4b161b add Amiga boing ball example, plus more pet syslib definitions, and pet diskio beginnings 2026-01-19 22:47:58 +01:00
Irmen de Jong 4971ebd41f pet32: added floats support! 2026-01-18 23:32:19 +01:00
Irmen de Jong e2276f000c fix alias issue when it targeted multi variable declaration 2026-01-18 19:48:37 +01:00
Irmen de Jong 520a12194e cx16: add new X16Edit rom entrypoint x16edit_loadfile_options2() that also takes starting line number 2026-01-18 19:10:06 +01:00
Irmen de Jong 6c61e1e5e8 fix alias target scoping problem 2026-01-17 21:39:39 +01:00
Irmen de Jong a385412e70 smaller floats.internal_long_to_float() 2026-01-17 20:38:30 +01:00
Irmen de Jong 928443469a strings: implement compare_nocase and compare_nocase_iso for 6502 2026-01-17 17:54:29 +01:00
Irmen de Jong 823f1cd6e2 strings: removed ncompare 2026-01-17 17:29:21 +01:00
Irmen de Jong 3009ddaaab strings: adding compare_nocase and compare_nocase_iso 2026-01-17 17:01:34 +01:00
Irmen de Jong bc0aecb1af cx16 diskio: way simpler solution for case insensitive file name listing 2026-01-17 01:02:38 +01:00
Irmen de Jong 573702111e attempt at improving compilation speed by caching scopedName 2026-01-16 22:46:04 +01:00
Irmen de Jong eaab2cfe5f version 12.1 2026-01-16 21:19:05 +01:00
Irmen de Jong 666285a002 add petsnd to symboldumps 2026-01-16 00:38:33 +01:00
Irmen de Jong d477e9b2bd pet32: added VIA registers (in 'pet' namespace) and petsnd module to play sound. 2026-01-16 00:15:04 +01:00
Irmen de Jong d774bad2b4 docs for new case routines 2026-01-15 22:06:05 +01:00
Irmen de Jong e3bb09d2a1 diskio: case insensitive file/dir listings only on CX16 target, and based on ISO encoding 2026-01-14 23:12:12 +01:00
Irmen de Jong 112bce0ac0 cx16: added ISO lowercase and uppercase routines to strings module 2026-01-14 22:07:24 +01:00
Irmen de Jong aac7d68455 fix watch mode to clear file cache every cycle 2026-01-14 20:16:33 +01:00
Irmen de Jong 620793011e way faster long to float conversion routine, fixes #202 2026-01-13 22:55:37 +01:00
Irmen de Jong cb4ffaa028 added case-insensitive pattern matcher strings.pattern_match_nocase()
added various case-insensitive versions of the file and directory listing routines in diskio (the ones that take an optional pattern)
2026-01-13 01:33:46 +01:00
Irmen de Jong 8c4944d703 optimized long and float copying through pointers 2026-01-12 00:19:21 +01:00
Irmen de Jong f5fbb5f930 implement some missing float copy 2026-01-11 22:06:21 +01:00
Irmen de Jong 40ce40db32 irq doc 2026-01-11 19:00:37 +01:00
Irmen de Jong ee556434bc doc 2026-01-11 14:12:16 +01:00
Irmen de Jong 5cd175d54f optimize successive additions/subtractions and multiplications (with const) into just a single add/sub, mul
optimize var = simpleexpression |  var += expression    -->   var = simpleexpression + expression  (for any augmented operator)
2026-01-11 01:30:04 +01:00
Irmen de Jong b51e116b63 optimize expression: uwordvar + 256 and variants to only manipulate msb 2026-01-10 22:50:33 +01:00
Irmen de Jong 5c7ac0efb7 IR: remove broken loadi/storei optimization 2026-01-10 22:23:01 +01:00
Irmen de Jong 34b228cfca fix peek/poke error with const offset 2026-01-10 20:58:16 +01:00
Irmen de Jong 2b26952044 better error 2026-01-10 19:06:59 +01:00
Irmen de Jong db38c27224 optimized peeks with a ubyte variable offset 2026-01-10 00:22:39 +01:00
Irmen de Jong 560013e58b optimized pokes with a ubyte variable offset 2026-01-09 22:36:04 +01:00
Irmen de Jong cdb41f4352 better errors when multiplying string or array with bogus value 2026-01-09 18:43:41 +01:00
Irmen de Jong 13cd68ba96 IR: fix LOADI.f peephole optimization 2026-01-09 03:49:55 +01:00
Irmen de Jong f69b293b7f optimized some more cases of poke and peek 2026-01-09 02:57:12 +01:00
Irmen de Jong d4bf0bf5b7 optimized simple cases of pokel() 2026-01-09 01:21:11 +01:00
Irmen de Jong 8c6a95f5e2 optimized simple cases of v = peekl() 2026-01-08 23:37:06 +01:00
Irmen de Jong 6301a8b040 optimize msb(long<<8/16/24) into single byte accesses (skip the whole shift) 2026-01-08 22:38:07 +01:00
Irmen de Jong daa9b40883 optimize lsb(long>>8/16/24) into single byte accesses (skip the whole shift) 2026-01-08 03:21:57 +01:00
Irmen de Jong aa045bb383 optimized certain word and long shifts to get the lsb/msb 2026-01-08 02:45:09 +01:00
Irmen de Jong 9d7049a659 optimized long>>8, long>>16, long>>24 2026-01-08 00:31:39 +01:00
Irmen de Jong bbfb2eb367 optimized longvar = lptr^^ 2026-01-07 23:30:35 +01:00
Irmen de Jong 447271f3f2 implemented missing byte ptr assignment 2026-01-07 22:42:53 +01:00
Irmen de Jong 78d6d2d59a breaking change: renamed R0R1_32 etc combined register parameters to just R0R1, R2R3, etc etc (_32 suffix is removed to make it more consistent with the other existing register names) 2026-01-07 21:56:16 +01:00
Irmen de Jong c6cd39b4ab slighty improved fix len() error message for pointer types 2026-01-07 21:37:07 +01:00
Irmen de Jong cd34c4eed0 fix len() error message for pointer types 2026-01-07 03:25:52 +01:00
Irmen de Jong 652b585ea4 code style 2026-01-07 00:48:45 +01:00
Irmen de Jong 2913fa47b2 fix pointer assignment TODO 2026-01-07 00:31:06 +01:00
Irmen de Jong d6e4175e21 fix msb(a+b), lsb(a+b) 2026-01-07 00:20:49 +01:00
Irmen de Jong 3a0add60fd optimize msb(lsw(longvar)) into @(&longvar+1) 2026-01-06 22:58:45 +01:00
Irmen de Jong bc4470aeda remove bsb() again - too confusing? 2026-01-06 22:39:04 +01:00
Irmen de Jong 97dade3655 fix setmsb() on long types 2026-01-06 22:11:58 +01:00
Irmen de Jong 54f23387d0 added bsb(long) builtin function that returns the bank byte of the long value (bits 16-23) 2026-01-06 20:56:03 +01:00
Irmen de Jong 77161e0f39 avoid separate definitions for list of simple builtin functions 2026-01-06 18:19:15 +01:00
Irmen de Jong 70d99bebb0 on c64/c128, txt.getclr() now always returns 0-15 (upper 4 bits are cleared). fixes #204 2026-01-05 21:50:38 +01:00
Irmen de Jong 75156f7dbd fix optimized long comparison against 0 2026-01-05 19:08:56 +01:00
Irmen de Jong 9827be0047 optimize sgn(integer) comparisons 2026-01-05 18:33:26 +01:00
Irmen de Jong 495dd526e1 comment correction 2026-01-04 23:04:33 +01:00
Irmen de Jong 3d20bc13b5 Virtual target: fix strings.right() and strings.slice() 2026-01-04 20:55:02 +01:00
Irmen de Jong 6028cc49ab simplify when into if-else, if possible 2026-01-04 19:39:38 +01:00
Irmen de Jong 23d3fb96b9 improved parsing rule of constdecl 2026-01-02 23:36:55 +01:00
Irmen de Jong 51503e054a IR: improve use of loadfield for msb/lsb of struct fields 2026-01-02 22:10:10 +01:00
Irmen de Jong 3f6177cbb8 improved 6502 codegen for lsb/msb of struct field 2026-01-02 21:36:11 +01:00
Irmen de Jong c1c88e0327 improved 6502 codegen for memory byte access into structs 2026-01-02 21:03:13 +01:00
Irmen de Jong 18ba25d6a4 IR: optimize double ADD/SUB 2026-01-02 20:24:35 +01:00
Irmen de Jong e74162671a clarify doc, fix possible poke() ast rewrite error 2026-01-02 19:11:57 +01:00
Irmen de Jong 2290308e06 fix invalid 6502 instructions in math.diff. Fixes #203 2026-01-02 01:08:50 +01:00
Irmen de Jong 82693cc3ee optimized lsb(msb(longvar)) which grabs the bank byte from a long 2026-01-01 21:39:34 +01:00
Irmen de Jong 50b8cae519 sys.save_prog8_internals() now also correctly saves the SCRATCH_PTR temp var. Moved a bunch of common sys routines into separate shared_sys_functions.p8 module file 2025-12-31 12:10:04 +01:00
Irmen de Jong 3a74931a61 c128: added c128.fast() and c128.slow() to enable/disable the CPU's 2 MHz mode.
Also added c128.is80(), set80(), set40() to check and set 40/80 column screen mode
2025-12-30 23:53:58 +01:00
Irmen de Jong c87e920c61 IR: stick all variable elements in <VARS> to improve the xml file structure a bit 2025-12-30 22:17:29 +01:00
Irmen de Jong 2c2de8bfb3 fix IR peephole optimizer index off by 1 error and make SSA blocks configurable (still defaults to yes in this version) 2025-12-30 18:58:42 +01:00
Irmen de Jong d386343fe7 fix romable error check for arrays 2025-12-30 02:26:01 +01:00
Irmen de Jong 5b32cfd172 fix some obscure errors, improve some error messages 2025-12-28 23:02:03 +01:00
Irmen de Jong 46dffcfb96 cx16: sysinit now explicitly makes vera border color also black (0) 2025-12-28 17:18:43 +01:00
Irmen de Jong b156c876f0 fix long expression typecast error 2025-12-28 01:24:54 +01:00
Irmen de Jong 71b66f506d fix asmsub register order crash when using longs + something else 2025-12-28 01:08:10 +01:00
Irmen de Jong 28d087b8b4 psg2 tweaks 2025-12-27 17:47:40 +01:00
Irmen de Jong a57815153d Merge branch 'psg2-module'
# Conflicts:
#	examples/test.p8
2025-12-27 17:12:05 +01:00
Irmen de Jong f910f73f28 added txt.iso2petscii() and txt.iso2petscii_str() 2025-12-26 17:55:07 +01:00
Irmen de Jong 6635515bf0 fix subtracting from str pointer, this fixes strings.endswith() for instance 2025-12-26 15:15:15 +01:00
Irmen de Jong cc0425a2c4 detect circular aliases, also fix error message for aliased function call with wrong number of args 2025-12-26 15:04:55 +01:00
Irmen de Jong c7052a183e fixing wrong error message about wrong arg count with alias 2025-12-25 23:49:03 +01:00
Irmen de Jong d9a0f3cb4a add line numbers to certain error messages 2025-12-25 22:30:05 +01:00
Irmen de Jong 80d6eb232d bool variables were not converted to constants if that was possible 2025-12-25 17:39:54 +01:00
Irmen de Jong d9ffb10eb5 added psg2 module (cx16 only) 2025-12-25 01:22:38 +01:00
Irmen de Jong 463d53345d more helpful min max error message for floats 2025-12-24 17:19:47 +01:00
Irmen de Jong 4b1063530e define cx16.VERA_PSG_BASE, VERA_PALETTE_BASE, VERA_SPRITES_BASE as long consts into Vera VRAM
returning a bool is no longer allowed if the sub return type is byte
2025-12-24 03:20:53 +01:00
Irmen de Jong 9266943e74 fix pointer assign type error with cast to uword 2025-12-24 01:18:23 +01:00
Irmen de Jong 1e3ae2cb1d IR: just use loadhay to get the result from CALLI 2025-12-23 19:07:51 +01:00
Irmen de Jong 55bc242e8b implemented in-place long negation and some missing long to float casts 2025-12-23 15:30:39 +01:00
Irmen de Jong aa3bbbb867 tweaks to wav streaming examples 2025-12-23 05:32:28 +01:00
Irmen de Jong 45ad0911df emudbg.cpu_cycles() now returns a long type instead of 2 separate words 2025-12-23 02:02:34 +01:00
Irmen de Jong 946def4613 IR: fix call() / CALLI return value handling 2025-12-23 00:39:14 +01:00
Irmen de Jong 3000c399bd cx16: add support for VIA timer IRQ in the irq routines 2025-12-22 21:37:09 +01:00
Irmen de Jong 1682400988 fix missing cx16 virtual register symbols in asm file (bool and long variants)
fix actually relocating all of them in the cx16 module
pet32 and c128 targets now also relocate them to ZP if there is space
2025-12-22 17:56:25 +01:00
Irmen de Jong 6ad435df1f mention main block size restriction 2025-12-22 05:28:55 +01:00
Irmen de Jong cd433b2111 c128: disable f-keys macros on startup 2025-12-22 00:23:21 +01:00
Irmen de Jong a50eebb369 fixed the balls example to also work on the monochrome pet32 target 2025-12-21 22:27:54 +01:00
Irmen de Jong 13d4af59c7 c64+c128: improved speed of txt.setchr, txt.getchr, txt.setclr, txt.getclr, txt.setcc 2025-12-21 22:23:17 +01:00
Irmen de Jong b6f4f2adb7 c64+c128: improved speed of txt.setchr and txt.getchr 2025-12-21 22:23:17 +01:00
Irmen de Jong e57d9f59a2 fix long 0 argument to asmsub with 32-bits combined virtual register to pass it in 2025-12-21 22:23:17 +01:00
Irmen de Jong 071d5dc0c5 pet32: improved speed of txt.setchr and txt.getchr 2025-12-21 22:23:16 +01:00
Irmen de Jong 4030a1b9e5 cleanup, animals example removed because it's superseded by the pointers/animalgame example now 2025-12-16 16:48:34 +01:00
Irmen de Jong 0217d05c68 IR: loadfield/storefield now accept offsets up to 65535 2025-12-16 14:56:11 +01:00
Irmen de Jong 8553f3c9f1 IR: loadfield is a better name 2025-12-16 12:57:45 +01:00
Irmen de Jong d157d03ea0 IR: optimize load+add+load into single loadm with offset (also store)
update gradle wrapper to 9.2.0
2025-12-16 12:17:52 +01:00
Irmen de Jong 1799b5f00c updated bundled zsmkit version to 2.8 2025-12-15 18:41:28 +01:00
Irmen de Jong 2ddb5e11b7 updated symboldumps 2025-12-13 02:12:00 +01:00
Irmen de Jong 3aa8b9fd12 turn prefix - numbers into negative numbers asap to avoid type problems later 2025-12-13 01:13:52 +01:00
Irmen de Jong 51300196e8 fix more long comparisons with arrays, fixes #196 2025-12-12 21:01:51 +01:00
Irmen de Jong f7b2f19cba remove restriction about long comparison with expression 2025-12-11 21:43:53 +01:00
Irmen de Jong 014a82a1ee always put all struct types as .struct in asm code to make them all accessible for size and offsets 2025-12-10 21:39:44 +01:00
Irmen de Jong 745cf3d958 IR: removed loadix/storeix instructions 2025-12-09 22:55:53 +01:00
Irmen de Jong c4aa681d58 IR: better name for loadfield/storefield instructions 2025-12-09 00:40:26 +01:00
Irmen de Jong feb0774bbd in benchmark-c routines, use same CIA timer as the original C code did so the program size is better comparable 2025-12-08 01:08:33 +01:00
Irmen de Jong 2ccfb2ac90 fix double evaluation (sign extension) of word to long assignment. Fixes #201 2025-12-07 22:23:59 +01:00
Irmen de Jong 2191c4b7d8 remove redundant expression assignment before multiplying certain words 2025-12-07 14:06:59 +01:00
Irmen de Jong 39c1cf0c3c fix diskio lf_start_list_dirs() and lf_start_list_files() not processing the pattern argument 2025-12-07 05:11:08 +01:00
Irmen de Jong c693be0dd6 fix byte subtract from long
fix IR register check error for %ir segments
2025-12-06 20:40:42 +01:00
Irmen de Jong 0f433055a7 fix pointer dereference division and modulus , fixes #199 2025-12-05 22:19:54 +01:00
Irmen de Jong 76c95ba6fa some more obscure inplace modifications implemented 2025-12-05 21:00:14 +01:00
Irmen de Jong bd272a8567 optimize some specialized cases of long << 2025-12-05 01:08:19 +01:00
Irmen de Jong b38fff76e9 hack to work around struct name prefix inconsistency
see github issue 198
2025-12-03 21:07:08 +01:00
Irmen de Jong 07b7639ff2 better error msg 2025-12-02 22:56:40 +01:00
Irmen de Jong 6871561c09 fix parent node error in sizeof() 2025-12-01 23:21:14 +01:00
Irmen de Jong 77a71138ad fix typo r8r0sl -> r8r9sl 2025-11-30 22:05:57 +01:00
Irmen de Jong 165f94bc93 fix long expression <0 / >0 comparison 2025-11-30 16:57:05 +01:00
Irmen de Jong b75090f928 fix some register clobbering with long operations 2025-11-30 16:44:36 +01:00
Irmen de Jong 62b0c82f93 implement long <=0 and long >=0 comparisons
make sure the adpcm benchmark doesn't use random memory contents
2025-11-30 16:25:58 +01:00
Irmen de Jong b1ef863c7f make $8000000 a valid long integer (-2147483648) 2025-11-26 23:10:59 +01:00
Irmen de Jong e78345410f fix long-- high word wrap bug 2025-11-26 20:13:17 +01:00
Irmen de Jong 8b86f97aaa Implement struct field long and signed word bitshifts. Fixes #194 2025-11-25 22:28:28 +01:00
Irmen de Jong 93135774e6 fix IR signed bitshift right 2025-11-25 22:16:28 +01:00
Irmen de Jong a64f27c6b0 add cx16.r0r1sl, cx16.r2r3sl, ... that memory-map signed longs on the virtual registers 2025-11-25 21:13:03 +01:00
Irmen de Jong 69ef63c96d tweaks 2025-11-24 22:23:40 +01:00
Irmen de Jong df1a2a1611 also optimize BRA+RTS into just BRA
release 12.0
2025-11-23 15:18:05 +01:00
Irmen de Jong d19a3af9ed change some single use float global constants to their asm proc 2025-11-21 21:35:51 +01:00
Irmen de Jong 352c11ad9f optimize float<>0 into sgn(float)<>0 2025-11-21 00:57:43 +01:00
Irmen de Jong 9504711fc7 IR/VM: SGN sets status bits 2025-11-19 21:37:04 +01:00
Irmen de Jong 590feda903 IR: fix sgn(float) register type error 2025-11-19 21:11:31 +01:00
Irmen de Jong 6e7e2922bf moved the float <> long cast routines to float.asm where the other cast routines are too 2025-11-19 18:45:32 +01:00
Irmen de Jong 01df1f0083 way more efficient implementation of internal_cast_as_long now using QINT 2025-11-19 18:32:40 +01:00
Irmen de Jong 397299bd1d implement 6502 codegen for casting float to long (super slow...) 2025-11-19 00:05:18 +01:00
Irmen de Jong c275aacd38 implement 6502 codegen for casting long to float 2025-11-18 23:26:12 +01:00
Irmen de Jong 6a6e18773e C64: key repeat is now enabled at program startup, to fall in line with the default key repeat behavior on the C128 and X16. 2025-11-18 21:09:13 +01:00
Irmen de Jong 99e037489b IR: added float<>long casts, cx16: blink_cursor extapi tested in test.p8 2025-11-17 23:13:56 +01:00
Irmen de Jong fb5290e17b cx16: added diskio.f_fatlba() 2025-11-15 20:17:26 +01:00
Irmen de Jong 818774ab84 added conv.hex2long(), added more new X16 extapi calls (rom 49+) 2025-11-15 17:52:34 +01:00
Irmen de Jong d667312acc Merge branch 'refs/heads/12.1-SNAPSHOT'
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-11-15 15:15:06 +01:00
Irmen de Jong 64d0cd87a8 diskio.f_seek() and f_tell() now use longs instead of separate words
also fix bug when returning multiple long values
2025-11-15 15:13:26 +01:00
Irmen de Jong 2fffbdde89 todo 2025-11-14 20:05:46 +01:00
Irmen de Jong dc8ea31c49 fix codegen error for a word array equality test 2025-11-13 21:18:04 +01:00
Irmen de Jong f4ead66e91 fix param name breakage in PET txt.setcc for ignored charcolor parameter 2025-11-12 22:00:30 +01:00
Irmen de Jong 314e7f5691 doc updates 2025-11-10 23:42:29 +01:00
Irmen de Jong da31465b7f added the missing smallstack to buffers module 2025-11-10 00:21:13 +01:00
Irmen de Jong 397a907088 fix handling and errors of using long addresses in memread/memwrite 2025-11-09 18:17:57 +01:00
Irmen de Jong 50c6962e1f fix symbol lookup crash 2025-11-09 15:10:10 +01:00
Irmen de Jong b7d1fb1342 translate address-of into + expression for non-const address ptrs too,
also IR optimization to use immediate arithmetic opcodes if possible
2025-11-09 01:36:34 +01:00
Irmen de Jong afb458a7da fix crash in long address-of 2025-11-08 22:24:37 +01:00
Irmen de Jong da3c7f267f improved identifier const value handling if types differ 2025-11-08 20:34:51 +01:00
Irmen de Jong b7ba7c50b1 handle &ptr[offset] for non-uword ptr 2025-11-08 20:31:28 +01:00
Irmen de Jong bdbfe7048b updated library symboldumps 2025-11-08 00:21:53 +01:00
Irmen de Jong 79039b66d4 beta6 2025-11-07 23:40:03 +01:00
Irmen de Jong 833e463525 String indexing bound check now includes the terminating 0 character. Also fix negative indexes on strings.
fixes #190
2025-11-07 23:20:44 +01:00
Irmen de Jong 6611e4e092 improve error message 2025-11-07 01:22:41 +01:00
Irmen de Jong 1f31cb18e4 IR: fix missing source lines in p8ir file 2025-11-06 00:39:31 +01:00
Irmen de Jong bc8ba252a5 made Position line, col, endcol all 1-based
added a precise (but slow) back face culling routine to the cobra-mk3 example
2025-11-05 00:25:43 +01:00
Irmen de Jong c353dd40bf put on..call jumplist in correct scope, fixes #197 2025-11-03 23:46:49 +01:00
Irmen de Jong 928ef6bbaa fix on..call parse problem, also struct name cannot be a keyword 2025-11-03 21:21:07 +01:00
Irmen de Jong 45dde856c6 upgrade to kotlin 2.2.21 2025-11-02 20:08:59 +01:00
Irmen de Jong e1ccef4e89 sort the library routines better, updated result of crc32 benchmark 2025-11-02 16:58:49 +01:00
Irmen de Jong 4d3f0ec223 sizeof(string) is now defined 2025-11-01 21:19:30 +01:00
Irmen de Jong b73c958c4a ir immediate syntax fixes 2025-11-01 16:55:00 +01:00
Irmen de Jong 2eac457e1c clarify string assignment (or rather, the lack thereof) 2025-11-01 00:40:52 +01:00
Irmen de Jong 815ef7e654 add check for not yet implement long expression comparisons 2025-11-01 00:26:36 +01:00
Irmen de Jong 0cd8c4f87e add strings.ncopy and nappend (length limited) 2025-10-31 21:22:15 +01:00
Irmen de Jong b02a8ed954 strings can no longer be assigned by-value. Use strings.copy() instead. Fixes #189 2025-10-31 19:22:40 +01:00
Irmen de Jong b1e07f3fdb better error when trying to use a const pointer (which is not supported yet) 2025-10-30 20:58:21 +01:00
Irmen de Jong fc8727f81e allow pointer to pointer assignment in cast codegen, fixes #191 2025-10-30 19:35:44 +01:00
Irmen de Jong ec728bad52 fix certain float array assignment, fixes #193 2025-10-30 01:45:25 +01:00
Irmen de Jong a5e827e40f implement some more long array operations 2025-10-30 01:20:59 +01:00
Irmen de Jong 34061c5a63 implement inplace bitwise operations on long struct fields 2025-10-28 23:44:34 +01:00
Irmen de Jong 9c2bcab4a5 fix more ptr/long issues 2025-10-28 23:08:12 +01:00
Irmen de Jong 2f07f41664 detect self-referencing aliases 2025-10-23 21:24:14 +02:00
Irmen de Jong 7027304597 put some upper bounds on more internal compiler loops 2025-10-23 20:37:44 +02:00
Irmen de Jong ee75333891 implement more long assignments through pointers 2025-10-21 22:55:46 +02:00
Irmen de Jong 6a70fb0480 deal with invalid sqrt results (negative argument), allow sqrt of longs (like floats) 2025-10-21 21:01:03 +02:00
Irmen de Jong ebc738b132 'hack' to allow unsigned long constants such as $ffffffff to be assigned to longs without casts 2025-10-20 00:33:40 +02:00
Irmen de Jong e5939be0bd some more long operations implemented 2025-10-19 23:03:17 +02:00
Irmen de Jong 0c0affd1bf cx16 diskio.load_size() now returns full long size. Fixes #184 2025-10-19 21:18:46 +02:00
Irmen de Jong c7158fd968 implement more long operations on struct fields 2025-10-17 01:55:11 +02:00
Irmen de Jong de2f3f0d0d tweak TODO messages for longs 2025-10-16 23:20:34 +02:00
Irmen de Jong c0286e3349 added txt.size() to return text screen width and height in 1 call. Also fixed txt.width/height on the C128 (where SCREEN reports 1 less for them...) 2025-10-16 00:33:14 +02:00
Irmen de Jong 513ee25432 beta5 2025-10-15 18:29:36 +02:00
Irmen de Jong be74290ddc many more TODO messages now log proper source positions 2025-10-15 17:58:40 +02:00
Irmen de Jong a36501a9ed fix long bitwise expressions temp register usage 2025-10-14 02:57:48 +02:00
Irmen de Jong ee5d33a230 fix long if-expressions temp register usage 2025-10-14 02:16:42 +02:00
Irmen de Jong a8fd66cfdb fix long==0 long!=0 register usage 2025-10-14 01:49:42 +02:00
Irmen de Jong f5ce744748 undo the math crc param optimization that now causes the footgun warning when you import math. + doc fix about arithmetic expressions 2025-10-14 00:31:29 +02:00
Irmen de Jong 68066acdec changed (and fixed) msb(long) and lsb(long) 2025-10-13 21:34:03 +02:00
Irmen de Jong 6286035d89 support long +/- number in expressions 2025-10-13 02:50:48 +02:00
Irmen de Jong 9a37eb9146 save A and Y when storing longs 2025-10-13 02:12:07 +02:00
Irmen de Jong 307796f115 fixed some bugs in optimizing long values 2025-10-12 23:15:42 +02:00
Irmen de Jong e962431139 added bcd.addtol()/subfroml() for in-place long calculations
fix long overflow messages, IR register type error
2025-10-12 22:08:10 +02:00
Irmen de Jong b7620abc9a longs: fix msw() and lsw() 2025-10-12 19:49:36 +02:00
Irmen de Jong d2719d23f5 C128: adjust MEMTOP value to $C000 during runtime (41 Kb of available space because Basic ROM is banked out) 2025-10-12 15:02:36 +02:00
Irmen de Jong d69eead6d8 fix abs(long), mklong(), mklong2(), peekl(), pokel() 2025-10-12 14:17:27 +02:00
Irmen de Jong 6db3611d93 fix typecast error when assigning pointer to long 2025-10-12 12:44:28 +02:00
Irmen de Jong a84320c7e2 add sgn(long) support, fix sgn() result type in IR 2025-10-12 12:22:32 +02:00
Irmen de Jong dfc720557c fix some more long hex value comparisons 2025-10-12 04:40:50 +02:00
Irmen de Jong 32149d073a bitwise long exprs now use R12-R13 instead of R2-R3 2025-10-12 03:02:09 +02:00
Irmen de Jong fc38be6376 fix a const long value hex asm mistake, fix conv.str_l(0) empty output 2025-10-12 01:52:35 +02:00
Irmen de Jong 13f6efc1d1 long +/- long now use R12-R15 instead of R0-R3 2025-10-12 01:35:02 +02:00
Irmen de Jong 8296002887 fix combined reg asm name 2025-10-12 01:34:00 +02:00
Irmen de Jong 424b89f357 longs now returned in R14+R15 instead of R0+R1 2025-10-12 00:27:17 +02:00
Irmen de Jong 4f5590fbff doc 2025-10-11 21:37:59 +02:00
Irmen de Jong 598e70c49a allow negative values on poke and pokew without an explicit cast 2025-10-11 16:39:36 +02:00
Irmen de Jong a6d051b496 update syntax files 2025-10-11 16:15:34 +02:00
Irmen de Jong 796d3242e1 optimize word pointer indexing and fix long pointer indexing assignment (pokel) 2025-10-11 15:59:27 +02:00
Irmen de Jong 1e8ff6f82a warning in doc about problems when using long + R0/R1 together 2025-10-11 15:36:57 +02:00
Irmen de Jong 07bb5c36bd avoid fallback JSR peek for common case pointer+offset 2025-10-11 04:45:03 +02:00
Irmen de Jong 7e26ecb0b6 allow 'then' in if-expressions to separate condition and result value 2025-10-10 23:26:53 +02:00
Irmen de Jong 0c59ad70d4 fix aliased pointer deref problem 2025-10-10 22:13:24 +02:00
Irmen de Jong 53ce688cc5 fix pointer type check for R0-R15 subroutine arg 2025-10-10 00:54:52 +02:00
Irmen de Jong bb8b44d9d0 fix missing byte to long assignment, add c-bench-64 comparison 2025-10-10 00:01:54 +02:00
Irmen de Jong 51ae32d23e fix up todo 2025-10-09 21:14:06 +02:00
markjreed c873fac0dc feat: point doc source links to matching version (#179)
* feat: point doc source links to matching version

* fix: formatting, remove debug
2025-10-09 20:03:46 +02:00
Irmen de Jong f0a67fff8a added missing byte to long type assignment 2025-10-09 02:52:33 +02:00
Irmen de Jong f85ccd837d tweak conv.str_l() so that it properly reuses the conv.string_out buffer 2025-10-08 22:33:03 +02:00
Irmen de Jong 396fcbc927 implement missing long typecasts 2025-10-07 17:46:10 +02:00
Irmen de Jong 0f564b301d implement peekl() and pokel() 2025-10-07 00:53:00 +02:00
Irmen de Jong f4f34fc2ed added symboltable into CompilationResult because it might be useful to inspect in tests 2025-10-07 00:26:43 +02:00
Irmen de Jong f7639cb78f implement long comparisons > and <= 2025-10-06 23:42:40 +02:00
Irmen de Jong f5f5aef722 implement long comparisons < and >= 2025-10-06 22:30:17 +02:00
Irmen de Jong d6e30d8468 fix signed word and long cmp() 2025-10-06 22:05:34 +02:00
Irmen de Jong 37afdf5a18 tweak antlr grammar to improve parsing performance 2025-10-06 01:28:33 +02:00
Irmen de Jong 5eb7074172 fix gfx_lores and gfx_hires disc() inaccurracy 2025-10-05 23:21:19 +02:00
Irmen de Jong 9aff280d10 fix monogfx.disc() inaccurracy 2025-10-05 23:03:53 +02:00
Irmen de Jong 44741a8e32 fix c64 graphics.disc() inaccurracy 2025-10-05 22:57:39 +02:00
Irmen de Jong 37535f2913 optimize charfade example, added textspotlight example 2025-10-05 21:18:47 +02:00
Irmen de Jong ec9475c308 use pointer arithmetic instead of raw peeks and pokes 2025-10-05 18:38:01 +02:00
Irmen de Jong b961ce97d6 add -libdump and -libsearch compiler options 2025-10-05 17:07:55 +02:00
Irmen de Jong 4ed92d71a7 remove "@split" tag
The default is to split word arrays. If you need your word array to not be split, use @nosplit on the array.
2025-10-05 17:06:21 +02:00
Irmen de Jong 3e1386a987 remove the -dontsplitarrays compiler option
it was still there for backward compatibility reasons with really old prog8 code. If you need a word array to be not split, just use @nosplit on the array.
2025-10-05 14:44:29 +02:00
Irmen de Jong 4195e3968a remove needless library module imports in unit tests 2025-10-05 13:10:02 +02:00
Irmen de Jong 961095bfec fix output path for shadow jar 2025-10-04 23:31:52 +02:00
Irmen de Jong 3ea8ca59b7 beta 4 now with support for long values 2025-10-04 22:55:18 +02:00
Irmen de Jong 71ffbe2ba7 document the long type 2025-10-04 22:33:43 +02:00
Irmen de Jong e63921009c added math.mul32(), verafx.muls now returns long 2025-10-04 21:54:53 +02:00
Irmen de Jong db1aa3f257 math.crc32() now returns the crc value as a long 2025-10-04 17:40:49 +02:00
Irmen de Jong 8abdb837b2 fix long equality comparisons 2025-10-04 17:21:39 +02:00
Irmen de Jong cdeabd4b66 only * and / 2025-10-04 05:13:18 +02:00
Irmen de Jong efff74c0f1 added sys.pushl() and sys.popl() 2025-10-04 03:05:50 +02:00
Irmen de Jong 0e177cb531 added cbm.RDTIML and cbm.SETTIML wrappers 2025-10-04 02:00:24 +02:00
Irmen de Jong 35b921b062 added bcd module for decimal addition and subtraction using BCD mode 2025-10-03 23:36:17 +02:00
Irmen de Jong a7d98e43b8 cmp(long, long) 2025-10-03 22:41:59 +02:00
Irmen de Jong 845ee2dd83 smaller code for several long operations 2025-10-03 21:25:13 +02:00
Irmen de Jong f9b0bfe31b implement rol() and ror() on longs (also rol2 and ror2 and abs) 2025-10-03 01:22:24 +02:00
Irmen de Jong f303d15628 fix long equality checks 2025-10-03 01:22:24 +02:00
Irmen de Jong 67f7ffa52d multi value returns with longs 2025-10-03 01:22:24 +02:00
Irmen de Jong 88c5d9783a long params and return values 2025-10-03 01:22:24 +02:00
Irmen de Jong e7fc0360ad long bitwise operator expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong cd1862dd9f fix signed long shift right 2025-10-03 01:22:24 +02:00
Irmen de Jong 045c4909a8 long equality comparisons 2025-10-03 01:22:24 +02:00
Irmen de Jong f1bfe619b2 avoiding endless loop in optimizer 2025-10-03 01:22:24 +02:00
Irmen de Jong e0107bacbd implement long << >> expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong b3bd2a6a09 fixed a bunch of long type handling 2025-10-03 01:22:24 +02:00
Irmen de Jong ff1f58e022 implement simple + / - long expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong 557b12668d implement print_ulhex(long, prefix) 2025-10-03 01:22:24 +02:00
Irmen de Jong b058f1c7c2 implement mklong(a,b,c,d) and mklong2(w1,w2) 2025-10-03 01:22:24 +02:00
Irmen de Jong 3e07b6ca70 adding long arrays 2025-10-03 01:22:24 +02:00
Irmen de Jong d66dc664de work on longs 2025-10-03 01:22:24 +02:00
Irmen de Jong a2b9d78cf3 start with introducing LONG datatype (32 bits signed integer) 2025-10-03 01:22:24 +02:00
Irmen de Jong 44f70da113 upgrade to JDK 17 for the build process (required by Gradle 9) 2025-10-03 00:18:55 +02:00
Irmen de Jong c87eaf576d buildfix and update to Gradle 9.1.0 2025-10-03 00:11:52 +02:00
Irmen de Jong c09b1395f6 IR: add a unit test for the SSA basic block change 2025-10-01 22:49:51 +02:00
Irmen de Jong d6a8201291 IR: fix SQRT reg1 type 2025-10-01 20:28:38 +02:00
Irmen de Jong 4187f97f7a IR: rename <CODE> to <CHUNK> , put the code lines in a new sub node <CODE> for improved xml structure 2025-10-01 19:32:05 +02:00
Irmen de Jong 9d3b2f12fd IR code blocks now better SSA basic blocks (ending with single branch instruction) 2025-10-01 19:08:44 +02:00
Irmen de Jong 87c1bbbf40 readme about LSP 2025-10-01 01:15:00 +02:00
Irmen de Jong a7ad6abdb9 Merge branch 'languageServer'
# Conflicts:
#	examples/test.p8
2025-09-30 22:14:03 +02:00
Irmen de Jong a611406020 IR: add compilerversion attribute to p8ir file 2025-09-30 22:07:04 +02:00
Irmen de Jong 75da38224d IR: make LSIG,MSIG,CONCAT instruction set flags to skip cmp #0 afterwards (if msb(x)>0) 2025-09-30 21:48:57 +02:00
Irmen de Jong 8d6f3301c8 conv.any2uword() return values have been changed to be more useful and convenient
now returns both the actual value and the number of characters, and the return values for the virtual target now matches the others.
2025-09-30 20:44:02 +02:00
Irmen de Jong 86b52a1c5e fix endless loop in rewriting type of const long values 2025-09-29 22:28:13 +02:00
Irmen de Jong 2c8b1c2022 moved cx16.cpu_is_65816() to sys.cpu_is_65816(). It know also does proper detection on the C64 and C128 like on the X16, because those two computers can also have this CPU via a SuperCPU expansion. 2025-09-29 21:37:26 +02:00
Irmen de Jong ab1f065752 first setup of LSP languageserver 2025-09-27 15:27:37 +02:00
Irmen de Jong 2c7256a443 support assignment to indexed pointer targets 2025-09-27 14:52:04 +02:00
Irmen de Jong 97420b28e5 preparing to support assignment to indexed pointer targets 2025-09-27 11:03:55 +02:00
Irmen de Jong 1467c7039d fix pointer deref ast printer 2025-09-27 10:42:55 +02:00
Irmen de Jong d319badc6c default 0 for long type 2025-09-25 21:32:23 +02:00
Irmen de Jong 65d6c1c438 fix IR instruction R/W usage counter 2025-09-25 01:50:27 +02:00
Irmen de Jong abeefb5655 improved pointer[0] 2025-09-24 22:26:09 +02:00
Irmen de Jong 50fecbcebe c64 sprite multiplexer WIP 2025-09-24 21:11:08 +02:00
Irmen de Jong 4fe8b72d42 fix broken uword comparison and asm peephole optimization 2025-09-24 02:42:26 +02:00
Irmen de Jong f3b060df51 no longer save Y register when loading value through ZP pointer LDA (zp),Y 2025-09-23 22:59:01 +02:00
Irmen de Jong 09d1cb6925 fix crash when indexing on a label or subroutine name
working on multiplexer
2025-09-23 20:07:52 +02:00
Irmen de Jong 54fa72fa98 added sys.waitrasterline() routine like sys.waitvsync() but wait for a given raster line
optimize uword <= $xx00 into msb(uword)<$xx
2025-09-23 01:09:54 +02:00
Irmen de Jong fd62fe7511 fix crash on invalid type cast added to ptr deref expression ('.' operator) 2025-09-22 23:54:01 +02:00
Irmen de Jong bfb34dff62 fix recursive var decl error in case of some pointer derefs 2025-09-22 23:45:59 +02:00
Irmen de Jong 817b623596 optimize uword >= $xx00 into msb(uword)>=$xx 2025-09-22 22:59:56 +02:00
Irmen de Jong f6dbeb1f63 working on a more complex c64 sprite multiplexer 2025-09-22 21:58:12 +02:00
Irmen de Jong c4b9bdd33f proper pointer array initializer size checking, nicer sprite movement in simplemultiplexer 2025-09-22 20:07:38 +02:00
Irmen de Jong 68e0d5f1b5 add sys.set_rasterline on c64 and c128 targets as well (cx16 already had it) 2025-09-21 01:36:43 +02:00
Irmen de Jong 4939e3df55 add sys.update_rasterirq, more robust sei/cli handling, added simple c64 sprite multiplexer example 2025-09-21 00:28:33 +02:00
Irmen de Jong 19f19f3880 doc and opening borders 2025-09-20 21:44:48 +02:00
Irmen de Jong ad0c767ea8 %breakpoint! introduced to place after an assignment to make it parse correctly 2025-09-20 00:46:15 +02:00
Irmen de Jong ed5f4d5855 fix missing subroutine argument list check 2025-09-19 05:35:03 +02:00
Irmen de Jong c2f5d37486 new cx16/charfade.p8 example 2025-09-19 04:44:08 +02:00
Irmen de Jong 231b50dacb optimize certain word multiplication with bit shifting if it's a power of 2 2025-09-18 20:57:26 +02:00
Irmen de Jong a71895cbe8 optimize pointer.field += 1 into pointer.field INC/DEC 2025-09-18 19:27:36 +02:00
Irmen de Jong a8bede17b2 fix defer() with the arena allocator ("return values are evaluated before the defer is executed") 2025-09-17 23:59:32 +02:00
Irmen de Jong f6c8e693a5 add offsetof() 2025-09-17 23:30:15 +02:00
Irmen de Jong 9461e4088c fixed the hashtable example and workarounds for misbehaving defer in allocators 2025-09-17 22:53:01 +02:00
Irmen de Jong efd73fd10d implement some more in place pointer operators 2025-09-17 18:19:03 +02:00
Irmen de Jong ddf6e84a1a fix type checks for wrong pointer types in pointer array initalizer and assignment 2025-09-16 21:18:29 +02:00
Irmen de Jong 633d6c34e2 add support for struct pointers and short-form initializers in arrays and assignments 2025-09-16 20:37:50 +02:00
Irmen de Jong 6e7fbc6683 fix crash when invalid struct name is used in pointer decl 2025-09-15 20:39:21 +02:00
Irmen de Jong 124ea1230b fix vm to understand struct instances in arrays 2025-09-14 19:04:01 +02:00
Irmen de Jong 8b48a295b6 allow struct initializers to occur in array literals 2025-09-14 18:16:46 +02:00
Irmen de Jong d285d37fdb fix clobbers syntax in symboldumper 2025-09-13 21:47:52 +02:00
Irmen de Jong 8bb927b483 fix compiler crash with on..call statement in nested scope 2025-09-13 06:44:49 +02:00
Irmen de Jong 1af4cd0d63 fix struct initializer error checking 2025-09-12 19:55:12 +02:00
Irmen de Jong db2f28c4cd add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)
fix nullpointer in array initalizer
2025-09-12 17:09:44 +02:00
314 changed files with 23602 additions and 7590 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
description: Startup workflow to load project context
---
1. Read the file `QWEN.md` in the project root to understand the project structure, module descriptions, and development commands.
+17
View File
@@ -0,0 +1,17 @@
# An .aiignore file follows the same syntax as a .gitignore file.
# .gitignore documentation: https://git-scm.com/docs/gitignore
# you can ignore files
.DS_Store
*.log
*.tmp
*.bin
*.prg
# or folders
dist/
build/
out/
output/
.gradle/
.kotlin/
+2 -2
View File
@@ -20,10 +20,10 @@ jobs:
make -j4
sudo make install
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: temurin
- name: Build and test with Gradle
-16
View File
@@ -534,7 +534,6 @@
<inspection_tool class="GroovyUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedIncOrDec" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyVariableNotAssigned" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrpcSchemes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="Guava" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HardcodedFileSeparators" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_recognizeExampleMediaType" value="false" />
@@ -585,20 +584,6 @@
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlWrongAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientDuplicateImportInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientInappropriateProtocolUsageInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientRunRequestNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientUnresolvedAuthId" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="HttpClientUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestContentLengthIsIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestCustomHttpMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestEnvironmentAuthConfigurationValidationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestJsonBodyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestPlaceholder" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorJsonBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorXmlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorYamlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestWhitespaceInsideRequestTargetPath" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
<option name="insideTryAllowed" value="false" />
@@ -629,7 +614,6 @@
<option name="reportPerFile" value="true" />
<option name="kotlinOnly" value="true" />
</inspection_tool>
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncrementDecrementUsedAsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="InfiniteLoopJS" enabled="false" level="WARNING" enabled_by_default="false" />
+10 -10
View File
@@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0.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/2.2.20/kotlin-stdlib-jdk7-2.2.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-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/2.2.20/kotlin-stdlib-jdk7-2.2.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-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/2.2.20/kotlin-stdlib-jdk7-2.2.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+1
View File
@@ -15,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
+32
View File
@@ -0,0 +1,32 @@
# Agent Instructions for Prog8 Project
## Required Startup Actions
When starting a new session with this project, you **MUST** perform these actions before proceeding with any user requests:
1. **Read QWEN.md** - Execute: `read /home/irmen/Projects/prog8/QWEN.md`
- This file contains critical project context including:
- Project overview (Prog8 compiler for 8-bit retro systems)
- Module descriptions for all 20+ modules
- Gradle commands and build instructions
- Prog8 compiler usage examples
- Development shortcuts
2. **Check startup workflow** - Execute: `read /home/irmen/Projects/prog8/.agent/workflows/startup.md`
- This file may contain additional startup instructions
## Why This Matters
The prog8 project is a complex compiler with:
- Multiple code generation backends (6502, IR, Virtual Machine)
- 20+ interconnected modules
- Specific build and development workflows
- Proprietary language syntax and conventions
Without reading QWEN.md first, you will lack essential context needed to:
- Understand module relationships
- Use correct gradle commands
- Reference the right files
- Follow project conventions
**DO NOT PROCEED** with any user requests until you've read QWEN.md!
+86
View File
@@ -0,0 +1,86 @@
# Qwen Assistant Memory for prog8 Project
This file contains project-specific information that helps the Qwen assistant understand and work effectively with the prog8 codebase.
## Project Overview
- This project is a compiler for the Prog8 programming language.
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
- The compiler has a 6502 code generator backend, and an IR code generator.
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
- Prog8 source files have .p8 extension
- Prog8 source files are a "module" that can contain 1 ore more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
## Prog8 language feature hints
- an overview of the language features can be found in the documentation file docs/source/introduction.rst
- the syntax and grammar is specified in an ANTLR4 grammar file found in the parser directory
- the syntax for boolean logical operators is 'and', 'or', 'xor', 'not'. Bitwise operators are '&', '|', '^', '~', and '<<','>>' for bit shifting left and right respectively. All operators are documents in docs/source/programming.rst
- note that Prog8 does have a 4-byte signed long type and 5-byte "Microsoft" floating point support
- module imports are done using "%import modulename"
## Module Descriptions
- `beanshell` - EXPERIMENTAL/UNFINISHED Contains BeanShell integration for scripting capabilities within the compiler
- `benchmark-c` - C implementations for performance comparison and benchmarking
- `benchmark-program` - Benchmark programs to test compiler output performance
- `codeCore` - Core code generation utilities and shared components
- `codeGenCpu6502` - 6502 CPU-specific code generator backend (generates assembly for 6502-based systems)
- `codeGenExperimental` - Experimental code generators that are under development
- `codeGenIntermediate` - Intermediate representation (IR) code generator
- `codeGenVirtual` - Virtual machine code generator backend
- `codeOptimizers` - Optimization passes for improving generated code efficiency
- `compiler` - Main compiler executable and top-level compiler logic
- `compilerAst` - complicated Abstract Syntax Tree (AST) where most optimizations also run on, is later transformed into the simpleAst
- `docs` - Documentation files for the Prog8 language and compiler
- `examples` - Example Prog8 programs demonstrating language features
- `intermediate` - Components related to the intermediate representation (IR) of Prog8 programs
- `languageServer` - Language Server Protocol implementation for IDE integration
- `parser` - ANTLR4 Parser implementation for the Prog8 language syntax
- `scripts` - Utility scripts for development, testing, and deployment
- `simpleAst` - Simplified AST that is used to run the code generator backends from
- `syntax-files` - Syntax definition files for editors and IDEs
- `virtualmachine` - Virtual machine implementation that can execute IR code
## Key Information
- never read the files and directories that are ignored via the .aiignore and .gitignore files
- Current development focus areas: compiler optimizations, new language features, backend improvements
- Important project conventions: Kotlin for compiler implementation, modular architecture, IR-based compilation
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
### Gradle Commands
- use the system installed gradle command instead of the gradle wrapper.
- `gradle build` - Full build of the compiler including running the full test suite
- `gradle clean` - Clean build artifacts
- `gradle compileKotlin` - Compile only the Kotlin source code
- `gradle installdist` - Create the compiler JARs and executable file
- `gradle installshadowdist` - Create the single "fat" compiler JAR and executable file
### Development Shortcuts
- `gradle test --tests "*TestName*"` - Run specific test classes
- `gradle build --refresh-dependencies` - Refresh dependencies during development
### Prog8 Compiler Usage
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
- the `-emu` switch can be used to directly execute the resulting program in an emulator after successful compilation.
- `prog8c -target cx16 input.p8` - Compile a Prog8 source file "input.p8" for the CommanderX16 target
- `prog8c -target cx16 -check input.p8` - Quickly check a Prog8 source file "input.p8" for compiler errors, no output binary is produced
- `prog8c -target cx16 -emu input.p8` - Compile and execute a prog8 file in the CommanderX16 emulator
- `prog8c -target c64 -emu input.p8` - Compile and execute a prog8 file in the Commodore-64 emulator
- `prog8c -target virtual input.p8` - Compile a prog8 file for the IR/Virtual machine target
- `prog8c -target virtual -emu input.p8` - Compile and directly execute a prog8 file in the Virtual Machine
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
- `x16emu -scale 2 -prg input.prg` - Just load an existing compiled program in the CommanderX16 emulator
- `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator (Note: You can safely ignore errors and warnings from this emulator)
### Compilation Output Files
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
- `*.asm` - Generated assembly code from the Prog8 source
- `*.list` - Generated full assembly listing file from the Prog8 source
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
## Working Notes
[Any ongoing notes about the project that would be helpful for the assistant to remember]
## TODO Items
The file docs/source/todo.rst contains a comprehensive list of things that still have to be fixed, implemented, or optimized.
+17 -5
View File
@@ -34,8 +34,11 @@ How to get it/build it
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
Note that if you are not using *gradle* to build it, you might have to perform some manual
tasks once to make it compile fully. These are explained in the linked instructions.
- Alternatively, you can also install the compiler as a package on some linux distros:
- Alternatively, you can also install the compiler as a package on some linux distros (which will take care of dependencies automatically):
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
- Finally there's a Homebrew recipe for Mac OS (but also for Linux, and WSL2 on Windows, this also takes care of dependencies automatically):
``brew install prog8``
Community
---------
@@ -58,9 +61,9 @@ What does Prog8 provide?
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because it's compiled to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- various data types other than just bytes (16-bit words, long integers, floats, strings)
- Structs and typed pointers
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
@@ -82,7 +85,7 @@ What does Prog8 provide?
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
- 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether
- 50 Kb of available program RAM size on the C64 by default (41 Kb on the C128) because Basic ROM is banked out by default
*Rapid edit-compile-run-debug cycle:*
@@ -172,7 +175,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
}
when compiled an ran on a C-64 you'll get:
when compiled and ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
@@ -193,3 +196,12 @@ For instance here's a well known space ship animated in 3D with hidden line remo
in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)
Performance comparison with various C compilers
-----------------------------------------------
Here is a performance comparison with various C compilers for the 6502/C64 so you can get
an idea of how Prog8 stacks up.
[comparison](benchmark-c/benchmark.md)
+39
View File
@@ -0,0 +1,39 @@
# C-Bench-64 comparison
Several benchmarks of https://thred.github.io/c-bench-64/
have been ported to equivalent Prog8 code and the benchmark results have been penciled in in the graphs below.
Because they use the CIA timer for measuring the duration of the runs, the code only works on a C64.
Maybe one day I'll try to integrate the prog8 data properly but their benchmark site is comparing C compilers, which Prog8 clearly is not.
However conclusions so far (note: these are micro benchmarks so interpret the results as you will!)
* Prog8 program size is consistently one of the smallest.
* Prog8 execution speed places it more or less in the middle of the stack.
Measured with Prog8 V12.0 .
Benchmarks ran on a PAL C64 emulated through VICE.
textual results:
| benchmark | size | time |
|-----------|------|----------|
| crc8 | 1568 | 2.0 sec |
| crc16 | 1608 | 2.5 sec |
| crc32 | 1812 | 4.2 sec |
| pow | 2152 | 11.7 sec |
| sieve | 1772 | 22.1 sec |
| sieve_bit | 1918 | 40.1 sec |
![crc8](result-crc8.png)
![crc16](result-crc16.png)
![crc32](result-crc32.png)
![pow](result-pow.png)
![sieve](result-sieve.png)
![sieve-bit](result-sieve-bit.png)
+98
View File
@@ -0,0 +1,98 @@
%import textio
cia {
ubyte freq
const ubyte CNT = 200
sub calibrate() {
txt.print("calibrating frequency: ")
tod_init(0)
txt.print_ub(tod_freq())
txt.print(" hz\n")
tod_reset()
}
sub print_time() {
uword t = tod_get10()
txt.print("(cia) time: ")
txt.print_uw(t / 10)
txt.chrout('.')
txt.print_uw(t % 10)
txt.print(" sec.\n")
}
sub tod_reset() {
; set the tod to 0
c64.CIA2TODHR = 0
c64.CIA2TODMIN = 0
c64.CIA2TODSEC = 0
c64.CIA2TOD10 = 0
}
sub tod_get10() -> uword {
ubyte h, m, s, t
uword time
h = c64.CIA2TODHR
m = c64.CIA2TODMIN
s = c64.CIA2TODSEC
t = c64.CIA2TOD10
time = t
time += (s & $0f) * (10 as uword)
time += (s >> 4) * (100 as uword)
time += (m & $0f) * 600
time += (m >> 4) * 6000
return time
}
sub tod_init(ubyte f) {
if (f == 0)
freq = tod_detect_freq()
else
freq = f
if (freq == 50)
c64.CIA2CRA |= $80
else
c64.CIA2CRA &= $7f
}
sub tod_freq() -> ubyte {
return freq
}
sub tod_detect_freq() -> ubyte {
uword cbl
c64.CIA2CRB = $40 ; stop timer
c64.CIA2CRA = $80 ; stop timer
; set ta to overflow every 10000 count (~= 10ms)
c64.CIA2TAL = $10
c64.CIA2TAH = $27
c64.CIA2TBL = CNT
c64.CIA2TBH = 0
tod_reset()
c64.CIA2CRB = $41 ; input from tim1 overflow, continuous, start timer
c64.CIA2CRA = $81 ; start timer, continuous tod 50HZ
while c64.CIA2TODSEC == 0 {
; wait for tod to count 1s
}
; cal=CIA2.ta_lo;
; cah=CIA2.ta_hi;
cbl = c64.CIA2TBL
; cbh=CIA2.tb_hi;
; printf("count2 = %d %d %d %d\n",cah, cal, cbh, cbl);
; printf("elapsed ~= %d0ms\n",CNT-cbl);
if CNT - cbl > 90
return 50
else
return 60
}
}
+71
View File
@@ -0,0 +1,71 @@
%import textio
%import ciatimer
; note: Prog8 has a complete CRC16 routine in its library: math.crc16
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const uword EXPECTED = $ffd0
uword crc_result
sub benchmark_name()
{
txt.print("crc16.p8\n")
txt.print("Calculates the CRC16 of the C64 Kernal\n")
}
sub benchmark()
{
crc_result = CRC16($e000, $2000)
}
sub benchmark_check() -> bool
{
txt.print("CRC=")
txt.print_uwhex(crc_result, true)
if crc_result == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uwhex(EXPECTED, true)
txt.nl()
return true
}
sub CRC16(^^ubyte data, uword length) -> uword {
; CRC-16/XMODEM
uword crc
repeat length
{
crc ^= mkword(@(data),0) ; currently there's no easy way to affect only the MSB of the variable
repeat 8
{
crc <<=1
if_cs
crc ^= $1021
}
data++
}
return crc
}
}
+72
View File
@@ -0,0 +1,72 @@
%import textio
%import ciatimer
; note: Prog8 has a complete CRC32 routine in its library: math.crc32
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const long EXPECTED = $e1fa84c6
long crc_result
sub benchmark_name()
{
txt.print("crc32.p8\n")
txt.print("Calculates the CRC32 of the C64 Kernal\n")
}
sub benchmark()
{
crc_result = CRC32($e000, $2000)
}
sub benchmark_check() -> bool
{
txt.print("CRC=")
txt.print_ulhex(crc_result, true)
if crc_result == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ulhex(EXPECTED, true)
txt.nl()
return true
}
sub CRC32(^^ubyte data, uword length) -> long {
; CRC-32/CKSUM
long crc
repeat length
{
crc ^= (@(data) as long)<<24 ; currently there's no easy way to affect only the MSB of the variable
repeat 8
{
crc <<=1
if_cs
crc ^= $04c11db7
}
data++
}
crc ^= $ffffffff
return crc
}
}
+72
View File
@@ -0,0 +1,72 @@
%import textio
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
sub benchmark_name()
{
txt.print("crc8.p8\n")
txt.print("Calculates the CRC8 of the C64 Kernal\n")
}
sub benchmark()
{
crc_result = CRC8($e000, $2000)
}
sub benchmark_check() -> bool
{
txt.print("CRC=")
txt.print_ubhex(crc_result, true)
if crc_result == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ubhex(EXPECTED, true)
txt.nl()
return true
}
const ubyte EXPECTED = $a2
ubyte crc_result
sub CRC8(^^ubyte data, uword length) -> ubyte
{
; CRC-8/GSM-A
ubyte crc
repeat length
{
crc ^= @(data)
repeat 8
{
if (crc & $80) != 0
crc = (crc << 1) ^ $1d
else
crc <<= 1
}
data++
}
return crc
}
}
+93
View File
@@ -0,0 +1,93 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const ubyte N_ITER = 10
const ubyte SIZE = 32
float[SIZE] array
const float expected = 3614007361536.000000
const float epsilon = 10000000
float res
sub benchmark_name()
{
txt.print("pow.p8\n")
txt.print("Calculates floating point exponential (")
txt.print_uw(N_ITER)
txt.print(" iterations)\n")
}
sub benchmark()
{
ubyte i,j
res = 0
for j in 0 to SIZE-1 {
array[j]=0
}
for i in 0 to N_ITER-1 {
for j in 0 to SIZE-1 {
array[j] += testpow(2.5 / ((i + 1) as float), j)
}
}
for j in 0 to SIZE-1 {
res += array[j]
}
}
sub testpow(float x, ubyte y) -> float
{
float tmp = x
if y == 0
return 1
repeat y-1
tmp *= x
return tmp
}
sub benchmark_check() -> bool
{
txt.print("res = ")
txt.print_f(res)
float diff = expected - res;
txt.print("\nexpected = ")
txt.print_f(expected)
txt.print("\nepsilon = ")
txt.print_f(epsilon)
txt.print("\ndiff = ")
txt.print_f(diff)
if (diff < epsilon and diff > -epsilon)
{
txt.print(" [OK]\n")
return false
}
txt.print("[FAIL]\n")
return true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

+85
View File
@@ -0,0 +1,85 @@
%import textio
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const ubyte N_ITER = 10
const uword SIZE = 8191
const uword EXPECTED = 1900
uword prime_count
^^bool @zp flags = memory("flags", SIZE, 0)
sub benchmark_name()
{
txt.print("sieve.c\n")
txt.print("Calculates the primes from 1 to ")
txt.print_uw(SIZE * 2 + 2)
txt.print(" (")
txt.print_ub(N_ITER)
txt.print(" iterations)\n")
}
sub benchmark()
{
repeat N_ITER
prime_count = sieve(SIZE)
}
sub benchmark_check() -> bool
{
txt.print("count=")
txt.print_uw(prime_count)
if prime_count == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
sub sieve(uword size) -> uword
{
uword i, prime, k
uword count = 1
for i in 0 to size-1
flags[i] = true
for i in 0 to size-1
{
if flags[i]
{
prime = i + i + 3
k = i + prime
while k < size
{
flags[k] = false
k += prime
}
count++
}
}
return count
}
}
+107
View File
@@ -0,0 +1,107 @@
%import textio
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const ubyte N_ITER = 4
const uword SIZE = 16000
const uword EXPECTED = 3432
uword prime_count
^^ubyte @zp flags = memory("flags", SIZE/8+1, 0)
sub benchmark_name()
{
txt.print("sieve_bit.p8\n")
txt.print("Calculates the primes from 1 to ")
txt.print_uw(SIZE * 2 + 2)
txt.print(" (")
txt.print_ub(N_ITER)
txt.print(" iterations)\n")
}
sub benchmark()
{
repeat N_ITER
prime_count = sieve(SIZE)
}
sub benchmark_check() -> bool
{
txt.print("count=")
txt.print_uw(prime_count)
if prime_count == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
ubyte[] bitv = [
$01,
$02,
$04,
$08,
$10,
$20,
$40,
$80
]
sub check_flag(uword idx) -> bool
{
return flags[idx / 8] & bitv[lsb(idx) & 7] != 0
}
sub clear_flag(uword idx)
{
flags[idx / 8] &= ~(bitv[lsb(idx) & 7])
}
sub sieve(uword size) -> uword
{
uword i, prime, k
uword count=1
for i in 0 to (size / 8)
flags[i] = $ff
for i in 0 to size-1
{
if check_flag(i)
{
prime = i + i + 3
k = i + prime;
while k < size
{
clear_flag(k)
k += prime
}
count++
}
}
return count
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ adpcm {
while cbm.RDTIM16()<max_time {
adpcm.init(0,0)
uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input
uword @requirezp nibbles_ptr = $0800 ; for benchmark purposes, the exact nibbles don't really matter, so we just take this benchmark program itself as input
repeat 252/2 {
unroll 2 {
ubyte @zp nibble = @(nibbles_ptr)
+234
View File
@@ -0,0 +1,234 @@
; Binary Search Tree.
; It's a simple implementation for test/demonstration purposes of the pointer support;
; no balancing is done and memory is not freed when elements are removed.
%import textio
btree {
sub benchmark(uword max_time) -> uword {
txt.nl()
cbm.SETTIM(0,0,0)
uword score
while cbm.RDTIM16() < max_time {
bench_operations()
txt.chrout('.')
score++
}
txt.nl()
return score
}
sub bench_operations() {
arena.freeall()
btree.root = 0
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
btree.add(cx16.r0)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
void btree.contains(203)
void btree.contains(204)
void btree.contains(605)
void btree.contains(606)
btree.remove(9999)
btree.remove(97)
btree.remove(187)
btree.remove(203)
btree.remove(275)
btree.remove(321)
btree.remove(520)
btree.remove(562)
btree.remove(606)
btree.remove(719)
btree.remove(794)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
}
struct Node {
^^Node left
^^Node right
uword value
}
^^Node root = 0
sub add(uword value) {
^^Node node = arena.alloc(sizeof(Node))
node.value = value
node.left = node.right = 0
if root==0
root=node
else {
^^Node parent = root
repeat {
if parent.value >= value {
if parent.left!=0
parent = parent.left
else {
parent.left = node
return
}
} else {
if parent.right!=0
parent = parent.right
else {
parent.right = node
return
}
}
}
}
}
sub contains(uword value) -> bool {
^^Node r = root
while r!=0 {
if r.value==value
return true
if r.value>value
r = r.left
else
r = r.right
}
return false
}
sub size() -> ubyte {
ubyte count
if root!=0
count_node(root)
return count
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
count_node(r.left)
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
count_node(r.right)
r = sys.popw()
}
}
}
sub remove(uword value) {
; note: we don't deallocate the memory from the node, for simplicity sake
^^Node n = root
^^Node parent = 0
while n!=0 {
if n.value==value {
if n.left==0
replacechild(parent, n, n.right)
else if n.right==0
replacechild(parent, n, n.left)
else {
; Both left & right subtrees are present.
; N = node to delete.
; Find N's successor S. (N's right subtree's minimum element)
; Attach N's left subtree to S.left (S doesn't have a left child)
; Attach N's right subtree to Parent in place of N.
^^Node successor = find_successor(n)
successor.left = n.left
replacechild(parent, n, n.right)
}
return
}
parent = n
if n.value>value
n = n.left
else
n = n.right
}
sub find_successor(^^Node p) -> ^^Node {
^^Node succ = p
p = p.right
while p!=0 {
succ = p
p = p.left
}
return succ
}
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
if p.left==child
p.left = newchild
else
p.right = newchild
}
}
sub process_tree_inorder() {
if root!=0
process_tree(root)
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
process_tree(r.left)
r = sys.popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
process_tree(r.right)
r = sys.popw()
}
}
}
sub process_tree_preorder() {
if root!=0
process_tree(root,0)
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
}
}
}
}
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}
+5 -5
View File
@@ -472,7 +472,7 @@ elite_galaxy {
generate_next_planet()
textelite.num_commands++
}
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
init_market_for_planet()
}
@@ -494,7 +494,7 @@ elite_galaxy {
ubyte pi
for pi in 0 to 255 {
generate_next_planet()
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
if elite_util.prefix_matches(nameptr, elite_planet.name) {
ubyte distance = elite_planet.distance(x, y)
if distance < current_distance {
@@ -532,7 +532,7 @@ elite_galaxy {
; else
; txt.chrout('-')
; txt.spc()
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
elite_planet.display(true, distance)
}
pn++
@@ -548,7 +548,7 @@ elite_galaxy {
str current_name = " " ; 8 max
ubyte pn = 0
current_name = elite_planet.name
void strings.copy(elite_planet.name, current_name)
init(number)
; txt.clear_screen()
; txt.print("Galaxy #")
@@ -569,7 +569,7 @@ elite_galaxy {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= max_distance {
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
elite_planet.name[0] = strings.upperchar(elite_planet.name[0])
uword tx = elite_planet.x
uword ty = elite_planet.y
+20 -13
View File
@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -25,6 +26,7 @@ main {
str[20] benchmark_names
uword[20] benchmark_score
str version = "12.1"
sub start() {
@@ -34,8 +36,10 @@ main {
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n")
sys.wait(60)
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version ")
txt.print(version)
txt.print("\n\n")
sys.wait(120)
benchmark_number = 0
@@ -64,13 +68,9 @@ main {
benchmark_number++
announce_benchmark("circles with gfx_lores")
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_score[benchmark_number] = circles.draw(false, 400)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
@@ -79,28 +79,35 @@ main {
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
announce_benchmark("btree-struct-pointers")
benchmark_score[benchmark_number] = btree.benchmark(200)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
txt.print("\nscore benchmark (")
txt.print(version)
txt.print(")\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
txt.print("\n\ntotal score : ")
txt.print_uw(total_score)
txt.print(" (higher=better)\n")
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
+2 -2
View File
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
kotlin("jvm") version "2.2.20"
kotlin("jvm") version "2.3.0"
}
allprojects {
@@ -18,7 +18,7 @@ allprojects {
kotlin {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
freeCompilerArgs = listOf()
jvmTarget = JvmTarget.JVM_11
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
+2 -1
View File
@@ -5,9 +5,10 @@ import java.nio.file.Path
import kotlin.io.path.absolute
// the automatically generated module where all string literals are interned to:
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
@@ -37,6 +37,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val returns: ReturnConvention = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
@@ -45,6 +46,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
@@ -61,6 +63,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val paramConv = when(val paramType = actualParamTypes[0]) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R14R15, false)
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
@@ -86,60 +89,77 @@ class FSignature(val pure: Boolean, // does it have side effects?
}
val BuiltinFunctions: Map<String, FSignature> = mapOf(
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns a status in the carry flag, but not a proper return value
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"prog8_lib_copylong" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"prog8_lib_copyfloat" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
"sqrt__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"swap" to FSignature(false, null, FParam("var1", *NumericDatatypes + BaseDataType.BOOL), FParam("var2", *NumericDatatypes + BaseDataType.BOOL)),
"swap__byte" to FSignature(false, null, FParam("var1", BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL), FParam("var2",BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL)),
"swap__word" to FSignature(false, null, FParam("var1", BaseDataType.WORD, BaseDataType.UWORD), FParam("var2",BaseDataType.WORD, BaseDataType.UWORD)),
"swap__long" to FSignature(false, null, FParam("var1", BaseDataType.LONG), FParam("var2",BaseDataType.LONG)),
"swap__float" to FSignature(false, null, FParam("var1", BaseDataType.FLOAT), FParam("var2",BaseDataType.FLOAT)),
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
"lsb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
@@ -154,5 +174,11 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"divmod", "divmod__ubyte", "divmod__uword"
"divmod", "divmod__ubyte", "divmod__uword",
"swap", "swap__byte", "swap__word", "swap__long", "swap__float"
)
val SimpleBuiltinFunctions = setOf(
"msb", "lsb", "msw", "lsw",
"mkword", "mklong", "mklong2",
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
@@ -13,10 +13,12 @@ class CompilationOptions(val output: OutputType,
val noSysInit: Boolean,
val romable: Boolean,
val compTarget: ICompilationTarget,
val compilerVersion: String,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var memtopAddress: UInt,
var warnSymbolShadowing: Boolean = false,
var warnImplicitTypeCast: Boolean = false,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
@@ -28,7 +30,6 @@ class CompilationOptions(val output: OutputType,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var dontSplitWordArrays: Boolean = false,
var breakpointCpuInstruction: String? = null,
var ignoreFootguns: Boolean = false,
var outputDir: Path = Path(""),
+5 -2
View File
@@ -13,9 +13,12 @@ fun Number.toHex(): String {
// 256..65536 -> "$0100".."$ffff"
// larger -> "$12345678"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
val integer = this.toLong()
if(integer<0) {
if(integer==-2147483648L)
return "$80000000" // the exception to the rule, because -$80000000 is not a valid hex number
return '-' + abs(integer).toHex()
}
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
+52 -16
View File
@@ -50,17 +50,20 @@ enum class BaseDataType {
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
@@ -143,7 +146,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
@@ -222,6 +225,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -284,6 +288,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
@@ -302,14 +307,14 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
infix fun isAssignableTo(targetType: DataType) =
when(base) {
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
@@ -344,6 +349,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isUnsignedWord = base == BaseDataType.UWORD
val isSignedWord = base == BaseDataType.WORD
val isInteger = base.isInteger
val isWordOrByteOrBool = base.isWordOrByteOrBool
val isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
@@ -362,6 +368,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
@@ -394,7 +401,9 @@ enum class RegisterOrPair {
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
companion object {
val names by lazy { entries.map { it.toString()} }
@@ -407,6 +416,22 @@ enum class RegisterOrPair {
}
}
/**
* Returns the starting virtual register name for the current 32-bit combined virtual register
* @return The starting register name as a string, WITHOUT THE cx16 block scope prefix!
*/
fun startregname() = when(this) {
R0R1 -> "r0"
R2R3 -> "r2"
R4R5 -> "r4"
R6R7 -> "r6"
R8R9 -> "r8"
R10R11 -> "r10"
R12R13 -> "r12"
R14R15 -> "r14"
else -> throw IllegalArgumentException("must be a combined virtual register $this")
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
@@ -420,13 +445,14 @@ enum class RegisterOrPair {
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, null -> ""
else -> throw IllegalArgumentException("invalid register param type")
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
}
return listOf("cx16", name.lowercase()+suffix)
}
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
fun isLong() = this in CombinedLongRegisters
} // only used in parameter and return value specs in asm subroutines
@@ -463,6 +489,17 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val CombinedLongRegisters = arrayOf(
RegisterOrPair.R0R1,
RegisterOrPair.R2R3,
RegisterOrPair.R4R5,
RegisterOrPair.R6R7,
RegisterOrPair.R8R9,
RegisterOrPair.R10R11,
RegisterOrPair.R12R13,
RegisterOrPair.R14R15
)
val CpuRegisters = arrayOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
@@ -498,6 +535,5 @@ enum class ZeropageWish {
enum class SplitWish {
DONTCARE,
SPLIT,
NOSPLIT
}
@@ -4,7 +4,7 @@ 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 undefined(symbol: List<String>, suggestImport: Boolean=false, position: Position)
fun noErrors(): Boolean
fun report()
fun finalizeNumErrors(numErrors: Int, numWarnings: Int, numInfos: Int) {
@@ -39,7 +39,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
}
}
+5 -5
View File
@@ -1,10 +1,10 @@
package prog8.code.core
val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = arrayOf("and", "or", "xor", "not", "in")
val BitwiseOperators = arrayOf("&", "|", "^", "~")
val PrefixOperators = arrayOf("+", "-", "~", "not")
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
fun invertedComparisonOperator(operator: String) =
when (operator) {
+12 -3
View File
@@ -4,17 +4,26 @@ import prog8.code.sanitize
import prog8.code.source.SourceCode
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
import kotlin.io.path.toPath
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
override fun toString(): String = "[$file: line $line col ${startCol}-${endCol}]"
init {
if(!file.startsWith('~') ||!file.endsWith('~'))
require(line>0 && startCol>=0 && endCol>=startCol) {
"Invalid position: $this"
}
}
fun toClickableStr(): String {
if(this===DUMMY)
return ""
if(SourceCode.isLibraryResource(file))
return "$file:$line:$startCol:"
return try {
val path = Path(file).sanitize().toString()
"file://$path:$line:$startCol:"
val path = Path(file).sanitize().toUri().toString()
"$path:$line:$startCol:"
} catch(_: InvalidPathException) {
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
"file://$file:$line:$startCol:"
@@ -12,6 +12,11 @@ import kotlin.io.path.Path
object ImportFileSystem {
fun clearCaches() {
cache.clear()
lineSpanCache.clear()
}
fun expandTilde(path: String): String = if (path.startsWith("~")) {
val userHome = System.getProperty("user.home")
userHome + path.drop(1)
+2 -2
View File
@@ -19,7 +19,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
val flt = num.toDouble()
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $flt")
if (flt == 0.0)
return zero
@@ -40,7 +40,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent > 255 -> throw InternalCompilerException("floating point overflow: $flt")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
@@ -14,6 +14,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
@@ -1,9 +1,6 @@
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
import prog8.code.core.*
// reference: "Mapping the C128" zeropage chapter.
@@ -64,6 +61,41 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
val base = 0x0a // Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -83,16 +83,28 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt())
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -64,6 +64,19 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((2+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((3+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((2+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((2+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((2+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((2+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((2+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((2+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((2+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((2+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -57,7 +57,24 @@ class ConfigurableZeropage(
allocatedVariables["cx16.r${reg}H"] = VarAllocation(address+1u, DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation(address, DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation(address+1u, DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation(address, DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation(address+1u, DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove(address)
free.remove(address+1u)
}
}
if(virtualRegistersStart<0xffu) {
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
val start = virtualRegistersStart.toInt()
allocatedVariables["cx16.r0r1sl"] = VarAllocation((start + 0 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((start + 1 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((start + 2 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((start + 3 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((start + 4 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((start + 5 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((start + 6 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((start + 7 * 4).toUInt(), DataType.LONG, 4)
}
}
}
@@ -1,9 +1,6 @@
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
import prog8.code.core.*
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
@@ -47,6 +44,41 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02). TODO: is this valid on PET?
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -182,9 +182,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
fun findSubtypeReplacement(sub: ISubType): StStruct? {
if(updatedSt.lookup(sub.scopedNameString)!=null)
return null
val old = st.lookup(sub.scopedNameString)
if(old==null)
throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
val prefixed = ArrayDeque<String>()
var node: StNode = old
@@ -252,6 +250,14 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
newValue.add(newAddr)
}
}
is PtBuiltinFunctionCall -> {
// could be a struct instance or memory slab "allocation"
if (elt.name != "prog8_lib_structalloc" && elt.name != "memory")
throw AssemblyError("weird array value element $elt")
else {
newValue.add(elt)
}
}
else -> throw AssemblyError("weird array value element $elt")
}
}
@@ -326,8 +332,8 @@ class AsmGen6502Internal (
private var generatedLabelSequenceNumber: Int
) {
internal val optimizedByteMultiplications = arrayOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
internal val optimizedWordMultiplications = arrayOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
internal val loopEndLabels = ArrayDeque<String>()
private val zeropage = options.compTarget.zeropage
private val allocator = VariableAllocator(symbolTable, options, errors)
@@ -339,7 +345,7 @@ class AsmGen6502Internal (
private val anyExprGen = AnyExprAsmGen(this)
private val pointerGen = PointerAssignmentsGen(this, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, pointerGen, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, pointerGen,assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, pointerGen, assignmentAsmGen, errors)
private val ifExpressionAsmgen = IfExpressionAsmGen(this, assignmentAsmGen, errors)
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, assignmentAsmGen, this, pointerGen, allocator)
@@ -443,7 +449,7 @@ class AsmGen6502Internal (
if(line.length>=4 && invalid.matchesAt(line, 0)) {
errors.err(
"invalid assembly instruction used (not compatible with the 65816 CPU): ${line.trim()}",
Position("<output-assemblycode>", index, 1, 1)
Position("~output-assemblycode~", index+1, 1, 1)
)
}
}
@@ -521,7 +527,7 @@ class AsmGen6502Internal (
}
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier, tempZpPtrVar: String = "P8ZP_SCRATCH_PTR"): String {
// returns the source name of the zero page pointervar if it's already in the ZP,
// otherwise returns "P8ZP_SCRATCH_PTR" which is the intermediary
val symbol = symbolTable.lookup(pointervar.name)
@@ -542,25 +548,25 @@ class AsmGen6502Internal (
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
lda (P8ZP_SCRATCH_PTR)""")
"P8ZP_SCRATCH_PTR"
sta $tempZpPtrVar
sty $tempZpPtrVar+1
lda ($tempZpPtrVar)""")
tempZpPtrVar
}
} else {
return if (allocator.isZpVar((target as PtNamedNode).scopedName)) {
// pointervar is already in the zero page, no need to copy
loadAFromZpPointerVar(sourceName, true)
loadAFromZpPointerVar(sourceName)
sourceName
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
sta $tempZpPtrVar
sty $tempZpPtrVar+1
ldy #0
lda (P8ZP_SCRATCH_PTR),y""")
"P8ZP_SCRATCH_PTR"
lda ($tempZpPtrVar),y""")
tempZpPtrVar
}
}
}
@@ -609,7 +615,7 @@ class AsmGen6502Internal (
}
}
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean) {
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean=false) {
if (isTargetCpu(CpuType.CPU65C02))
out(" lda ($zpPointerVar)")
else {
@@ -750,6 +756,15 @@ class AsmGen6502Internal (
CpuRegister.Y -> out(" tay")
}
}
expr.type.isLong -> {
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out(" asl a | asl a")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
expr.type.isFloat -> {
if(options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${expr.position}")
@@ -798,6 +813,7 @@ class AsmGen6502Internal (
RegisterOrPair.AY,
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
in CombinedLongRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
else -> throw AssemblyError("invalid register")
@@ -805,6 +821,9 @@ class AsmGen6502Internal (
}
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
// this is basically the fallback assignment routine, it is RARELY called
when {
target.datatype.isByteOrBool -> {
if (value.asConstInteger()==0) {
@@ -832,7 +851,7 @@ class AsmGen6502Internal (
return
}
TargetStorageKind.POINTER -> {
TODO("assign to pointer ${target.position}")
pointerGen.assignByte(PtrTarget(target), 0)
return
}
else -> { }
@@ -842,7 +861,10 @@ class AsmGen6502Internal (
assignExpressionToRegister(value, RegisterOrPair.A)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, target.datatype.isSigned, false)
}
target.datatype.isPointer -> TODO("assign expression to pointer ${target.position}")
target.datatype.isPointer -> {
assignExpressionToRegister(value, RegisterOrPair.AX)
pointerGen.assignWordReg(PtrTarget(target), RegisterOrPair.AX)
}
target.datatype.isWord || target.datatype.isPassByRef -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
translateNormalAssignment(
@@ -852,6 +874,102 @@ class AsmGen6502Internal (
), value.definingISub()
)
}
target.datatype.isLong -> {
if(value is PtNumber) {
val hex = value.asConstInteger()!!.toLongHex()
when(target.kind) {
TargetStorageKind.VARIABLE -> {
out("""
lda #$${hex.substring(6,8)}
sta ${target.asmVarname}
lda #$${hex.substring(4, 6)}
sta ${target.asmVarname}+1
lda #$${hex.substring(2, 4)}
sta ${target.asmVarname}+2
lda #$${hex.take(2)}
sta ${target.asmVarname}+3""")
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
TargetStorageKind.POINTER -> pointerGen.assignLong(target.pointer!!, value.number.toInt())
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value is PtIdentifier && value.type.isLong) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
val valuesym = asmSymbolName(value)
out(
"""
lda $valuesym
sta ${target.asmVarname}
lda $valuesym+1
sta ${target.asmVarname}+1
lda $valuesym+2
sta ${target.asmVarname}+2
lda $valuesym+3
sta ${target.asmVarname}+3"""
)
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
TargetStorageKind.POINTER -> pointerGen.assignLongVar(target.pointer!!, asmSymbolName(value))
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value is PtTypeCast && value.type.isLong) {
if(value.value is PtIdentifier) {
val valuesym = asmSymbolName((value.value as PtIdentifier).name)
if(value.value.type.isByte) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
out(" lda $valuesym | sta ${target.asmVarname}")
signExtendLongVariable(target.asmVarname, value.value.type.base)
}
TargetStorageKind.ARRAY -> TODO("assign typecasted long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value.value.type.isWord) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
out("""
lda $valuesym
sta ${target.asmVarname}
lda $valuesym+1
sta ${target.asmVarname}+1""")
signExtendLongVariable(target.asmVarname, value.value.type.base)
}
TargetStorageKind.ARRAY -> TODO("assign typecasted long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in CombinedLongRegisters)
val startreg = target.register!!.startregname()
out("""
lda $valuesym
sta cx16.$startreg
lda $valuesym+1
sta cx16.$startreg+1""")
signExtendLongVariable("cx16.$startreg", value.value.type.base)
}
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else throw AssemblyError("weird casted type")
} else {
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
}
} else if(target.kind == TargetStorageKind.REGISTER) {
assignExpressionToRegister(value, target.register!!, true)
} else if(target.kind == TargetStorageKind.VARIABLE) {
assignExpressionToVariable(value, target.asmVarname, target.datatype)
} else {
TODO("assign long expression $value to a target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
}
}
target.datatype.isFloat -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignRegister(RegisterOrPair.FAC1, target)
@@ -1157,12 +1275,12 @@ $repeatLabel""")
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayVariable.name)!!
if(variable is StStaticVariable && variable.length!!<=128u)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
errors.info("the jump address array is split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
errors.info("the jump address array is @nosplit, but split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable
@@ -1182,7 +1300,7 @@ $repeatLabel""")
if(returnvalue!=null) {
val returnDt = sub.signature.returns.single()
if (returnDt.isNumericOrBool || returnDt.isPointer) {
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!)
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!, returnDt.isSigned)
}
else {
// all else take its address and assign that also to AY register pair
@@ -1203,7 +1321,7 @@ $repeatLabel""")
assignExpressionTo(it.first as PtExpression, tgt)
}
assigns.first().also {
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!)
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!, (it.first as PtExpression).type.isSigned)
}
}
out(" rts")
@@ -1289,6 +1407,53 @@ $repeatLabel""")
}
}
internal fun signExtendLongVariable(asmvar: String, valueDt: BaseDataType) {
// sign extend signed word in a var to a full long in that variable
when(valueDt) {
BaseDataType.UBYTE -> {
if(isTargetCpu(CpuType.CPU65C02)) {
out("""
stz $asmvar+1
stz $asmvar+2
stz $asmvar+3""")
}
else {
out("""
lda #0
sta $asmvar+1
sta $asmvar+2
sta $asmvar+3""")
}
}
BaseDataType.BYTE -> {
out("""
lda $asmvar
ora #$7f
bmi +
lda #0
+ sta $asmvar+1
sta $asmvar+2
sta $asmvar+3""")
}
BaseDataType.UWORD -> {
if(isTargetCpu(CpuType.CPU65C02))
out(" stz $asmvar+2 | stz $asmvar+3")
else
out(" lda #0 | sta $asmvar+2 | sta $asmvar+3")
}
BaseDataType.WORD -> {
out("""
lda $asmvar+1
ora #$7f
bmi +
lda #0
+ sta $asmvar+2
sta $asmvar+3""")
}
else -> throw AssemblyError("need byte or word type")
}
}
internal fun isZpVar(variable: PtIdentifier): Boolean = allocator.isZpVar(variable.name)
internal fun jmp(asmLabel: String, indirect: Boolean=false, indexedX: Boolean=false) {
@@ -1364,7 +1529,7 @@ $repeatLabel""")
val constOffset = (ptrAndIndex.second as? PtNumber)?.number?.toInt()
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
TODO("address-of array element ${addrOf.position}")
} else if(addrOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addrOf.position}")
} else {
@@ -1406,9 +1571,10 @@ $repeatLabel""")
val constOffset = (ptrAndIndex.second as? PtNumber)?.number?.toInt()
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
TODO("address-of array element ${addrOf.position}")
} else if(addrOf.dereference!=null) {
TODO("read &dereference")
if(pointerGen.readByteByAddressOfDereference(addrOf, constOffset))
return true
} else {
out(" lda ${asmSymbolName(addrOf.identifier!!)}+${constOffset}")
return true
@@ -1456,7 +1622,7 @@ $repeatLabel""")
val constOffset = (ptrAndIndex.second as? PtNumber)?.number?.toInt()
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
TODO("address-of array element ${addrOf.position}")
} else if(addrOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addrOf.position}")
} else {
@@ -1489,9 +1655,9 @@ $repeatLabel""")
val constOffset = (ptrAndIndex.second as? PtNumber)?.number?.toInt()
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
TODO("address-of array element ${addrOf.position}")
} else if(addrOf.dereference!=null) {
TODO("read &dereference")
TODO("read &dereference ${addrOf.position}")
} else {
out(" lda ${asmSymbolName(addrOf.identifier!!)}-${constOffset}")
return true
@@ -1836,9 +2002,9 @@ $repeatLabel""")
lda #<$leftName
ldy #>$leftName""")
if(lessOrEquals)
out("jsr floats.vars_lesseq_f")
out(" jsr floats.vars_lesseq_f")
else
out("jsr floats.vars_less_f")
out(" jsr floats.vars_less_f")
}
fun lessf(expr: PtExpression, rightName: String) {
assignExpressionToRegister(expr, RegisterOrPair.FAC1, true)
@@ -1957,6 +2123,23 @@ $repeatLabel""")
}
}
internal fun loadIndirectLongIntoCombinedLongRegister(zpPtrVar: String, offset: UByte, targetRegPair: RegisterOrPair) {
val regstart = targetRegPair.startregname()
out("""
ldy #$offset
lda ($zpPtrVar),y
sta cx16.$regstart
iny
lda ($zpPtrVar),y
sta cx16.$regstart+1
iny
lda ($zpPtrVar),y
sta cx16.$regstart+2
iny
lda ($zpPtrVar),y
sta cx16.$regstart+3""")
}
internal fun storeIndirectByte(byte: Int, zpPtrVar: String, offset: UByte) {
if (offset > 0u) {
out(" lda #$byte | ldy #$offset | sta ($zpPtrVar),y")
@@ -2093,7 +2276,7 @@ $repeatLabel""")
}
in Cx16VirtualRegisters -> {
val regname = regs.asScopedNameVirtualReg(DataType.UWORD)
val regname = regs.asScopedNameVirtualReg(DataType.UWORD).joinToString(".")
out("""
lda $regname
ldy #$offset
@@ -2160,7 +2343,7 @@ $repeatLabel""")
}
in Cx16VirtualRegisters -> {
val regname = regs.asScopedNameVirtualReg(DataType.UWORD)
val regname = regs.asScopedNameVirtualReg(DataType.UWORD).joinToString(".")
out("""
lda $regname
ldy #0
@@ -2259,6 +2442,34 @@ $repeatLabel""")
jsr floats.copy_float""")
}
internal fun storeIndirectFloatFP1(zpPtrVar: String, offset: UByte) {
val floattempvar = AsmAssignTarget(TargetStorageKind.VARIABLE, this, DataType.FLOAT, null, Position.DUMMY, variableAsmName = "floats.floats_temp_var")
assignRegister(RegisterOrPair.FAC1, floattempvar)
if(offset>0u) {
out("""
lda #<floats.floats_temp_var
ldy #>floats.floats_temp_var
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $zpPtrVar
ldy $zpPtrVar+1
clc
adc #$offset
bcc +
iny
+ jsr floats.copy_float""")
return
}
out("""
lda #<floats.floats_temp_var
ldy #>floats.floats_temp_var
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $zpPtrVar
ldy $zpPtrVar+1
jsr floats.copy_float""")
}
internal fun romableError(problem: String, pos: Position, assemblerShouldFail: Boolean = true) {
if(options.romable) {
@@ -2269,6 +2480,70 @@ $repeatLabel""")
}
}
}
internal fun pushLongRegisters(startreg: RegisterOrPair, numberOfLongs: Int) {
// may clobber P8ZP_SCRATCH_B1, X, Y.
if(numberOfLongs==0) return
val startregName = startreg.startregname()
if(numberOfLongs==1) {
out("""
tax
lda cx16.$startregName
pha
lda cx16.$startregName+1
pha
lda cx16.$startregName+2
pha
lda cx16.$startregName+3
pha
txa""")
return
}
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
out("""
tax
sty P8ZP_SCRATCH_B1
ldy #0
- lda cx16.$startregName,y
pha
iny
cpy #$numbytes
bne -
ldy P8ZP_SCRATCH_B1
txa""")
}
internal fun popLongRegisters(startreg: RegisterOrPair, numberOfLongs: Int) {
if(numberOfLongs==0) return
val startregName = startreg.startregname()
if(numberOfLongs==1) {
out("""
tax
pla
sta cx16.$startregName+3
pla
sta cx16.$startregName+2
pla
sta cx16.$startregName+1
pla
sta cx16.$startregName
txa""")
return
}
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
out("""
tax
sty P8ZP_SCRATCH_B1
ldy #$numbytes-1
- pla
sta cx16.$startregName,y
dey
bpl -
ldy P8ZP_SCRATCH_B1
txa""")
}
}
/**
@@ -2282,4 +2557,9 @@ internal class SubroutineExtraAsmInfo {
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<BaseDataType, String, UInt?>>()
}
}
internal fun Int.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
internal fun Long.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
internal fun Double.toLongHex(): String = this.toLong().toLongHex()
@@ -463,15 +463,19 @@ private fun optimizeStoreLoadSame(
mods.add(Modification(lines[2].index, true, null))
}
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated IF NOT IO ADDRESS
if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
) {
val firstVal = first.substring(4).trimStart()
val thirdVal = third.substring(4).trimStart()
if (firstVal == thirdVal)
mods.add(Modification(lines[3].index, true, null))
if (firstVal == thirdVal) {
val address = getAddressArg(third, symbolTable)
if (address != null && !machine.isIOAddress(address)) {
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
return mods
@@ -540,7 +544,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
val third = lines[2].value
if(!haslabel(second)) {
if ((" jmp" in first || "\tjmp" in first ) && (" rts" in second || "\trts" in second)) {
if ((" jmp" in first || "\tjmp" in first || " bra" in first || "\tbra" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[1].index, true, null)
}
else if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
@@ -580,7 +584,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
else if(" cmp #0" in second || "\tcmp #0" in second) {
// there are many instructions that modify A and set the bits...
for(instr in arrayOf("lda", "ora", "and", "eor", "adc", "sbc", "asl", "cmp", "inc a", "lsr", "pla", "rol", "ror", "txa", "tya")) {
for(instr in setOf("lda", "ora", "and", "eor", "adc", "sbc", "asl", "cmp", "inc a", "lsr", "pla", "rol", "ror", "txa", "tya")) {
if(" $instr" in first || "\t$instr" in first) {
mods.add(Modification(lines[1].index, true, null))
}
@@ -807,44 +811,44 @@ private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByF
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
->
->
clc
adc P8ZP_SCRATCH_PTR
sta P8ZP_SCRATCH_PTR
tya
adc P8ZP_SCRATCH_PTR+1
sta P8ZP_SCRATCH_PTR+1
adc P8ZP_SCRATCH_PTR
sta P8ZP_SCRATCH_PTR
tya
adc P8ZP_SCRATCH_PTR+1
sta P8ZP_SCRATCH_PTR+1
also SECOND SEQUENCE:
also SECOND SEQUENCE:
ldx VALUE/ ldy VALUE
sta SOMEWHERE_WITHOUT_,x_OR_,y
txa / tya
ldy #1
sta SOMEWHERE
-->
sta SOMEWHERE_WITHOUT_,x_OR_,y
lda VALUE
ldy #1
sta SOMEWHERE
sta SOMEWHERE_WITHOUT_,x_OR_,y
txa / tya
ldy #1
sta SOMEWHERE
-->
sta SOMEWHERE_WITHOUT_,x_OR_,y
lda VALUE
ldy #1
sta SOMEWHERE
also THIRD SEQUENCE:
ldx VALUE
ldy #0
sta SOMEWHERE_WITHOUT_,x
txa
iny
sta SOMEWHERE
ldy #0
sta SOMEWHERE_WITHOUT_,x
txa
iny
sta SOMEWHERE
-->
ldy #0
sta SOMEWHERE_WITHOUT_,x
lda VALUE
iny
sta SOMEWHERE
ldy #0
sta SOMEWHERE_WITHOUT_,x
lda VALUE
iny
sta SOMEWHERE
*/
val mods = mutableListOf<Modification>()
for (lines in linesByFourteen) {
@@ -1,6 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtAsmSub
import prog8.code.core.CombinedLongRegisters
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
@@ -8,18 +9,21 @@ import prog8.code.core.RegisterOrPair
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (order Y,X,A),
// 4) floating point registers (FAC1, FAC2),
// 5) CPU Carry status flag
// 1) combined cx16 virtual registers (longs),
// 2) cx16 virtual word registers,
// 3) paired CPU registers,
// 4) single CPU registers (order Y,X,A),
// 5) floating point registers (FAC1, FAC2),
// 6) CPU Carry status flag
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
val (cx16regs, args2) = args1.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 (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
combinedLongRegs.forEach { order += it.index }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
File diff suppressed because it is too large. Load diff
@@ -505,137 +505,158 @@ $endLabel""")
is StMemVar -> symbol.length!!
else -> 0u
}
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
fun iterateStrings() {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$endLabel""")
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$indexVar .byte 0
$endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
}
fun iterateBytes() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateSplitWords() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda ${iterableName}_lsb,y
sta $loopvarName
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateWords(actuallyLongs: Boolean = false) {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
lda $iterableName+2,y
sta $loopvarName+2
lda $iterableName+3,y
sta $loopvarName+3""")
}
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
cpy #${numElements*4u}
beq $endLabel
bne $loopLabel""")
} else {
asmgen.out("""
ldy $indexVar
iny
@@ -643,6 +664,19 @@ $loopLabel sty $indexVar
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
}
} else {
if(actuallyLongs) {
// array size is 64 longs, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
bne $loopLabel
beq $endLabel""")
} else {
// array size is 128 words, 256 bytes
asmgen.out("""
@@ -652,23 +686,29 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
iterableDt.isFloatArray -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
asmgen.out(endLabel)
}
when {
iterableDt.isString -> iterateStrings()
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
iterableDt.isSplitWordArray -> iterateSplitWords()
iterableDt.isWordArray -> iterateWords()
iterableDt.isLongArray -> iterateWords(true)
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.removeLast()
@@ -687,6 +727,11 @@ $loopLabel sty $indexVar
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
else if(iterableDt.isLongArray) {
if(range.last==range.first) return translateForSimpleLongRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleLongRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleLongRangeDesc(stmt, range)
}
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
@@ -795,7 +840,18 @@ $loopLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
iterableDt.isLongArray -> {
// loop over long range via loopvar, step >= 2 or <= -2
when(range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
else -> {
TODO("for long range. Report this error please, and use word loops in the meantime ${stmt.position}")
}
}
}
else -> throw AssemblyError("invalid range expression datatype")
}
asmgen.loopEndLabels.removeLast()
}
@@ -864,6 +920,7 @@ $loopLabel""")
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
require(range.step==1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
@@ -891,6 +948,7 @@ $loopLabel""")
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
require(range.step==-1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
@@ -927,6 +985,16 @@ $loopLabel""")
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
require(range.step==1)
TODO("for long range step +1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
}
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
require(range.step==-1)
TODO("for long range step -1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we consider them NOT to be optimized into (possibly different) CPU registers.
// Just load them in whatever the register spec says.
return when (params.size) {
1 -> params[0].register == null && (params[0].type.isIntegerOrBool || params[0].type.isPointer)
1 -> params[0].register == null && (params[0].type.isWordOrByteOrBool || params[0].type.isPointer)
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
else -> false
}
@@ -204,11 +204,15 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
return !arg.isSimple()
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
usesOtherRegistersWhileEvaluating(arg.args[0])
else if (arg.name == "mkword" || arg.name == "mklong2")
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
else if (arg.name == "mklong")
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
else
!arg.isSimple()
}
is PtAddressOf -> false
is PtIdentifier -> false
@@ -276,7 +280,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
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")
throw AssemblyError("argument type incompatible ${value.type} vs param ${parameter.value.type} at ${parameter.value.position}")
val paramRegister: RegisterOrStatusflag = when(sub) {
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
@@ -287,11 +291,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val requiredDt = parameter.value.type
if(requiredDt!=value.type) {
if(value.type.largerSizeThan(requiredDt))
throw AssemblyError("can only convert byte values to word param types")
throw AssemblyError("can only convert byte values to word param types ${value.position}")
}
if (statusflag!=null) {
if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte or bool value is required")
throw AssemblyError("for statusflag, byte or bool value is required ${value.position}")
if (statusflag == Statusflag.Pc) {
// this boolean param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@@ -319,7 +323,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.out(" ror a")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
} else throw AssemblyError("can only use Carry as status flag parameter ${value.position}")
return RegisterOrStatusflag(null, statusflag)
}
else {
@@ -359,12 +363,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType.isBool && paramType.isBool)
return true
// signed/unsigned args vs parameters are considered compatible
if(argType.isByte && paramType.isByte)
return true
if(argType.isWord && paramType.isWord)
return true
// only signed longs and floats are available so these were already covered
// if(argType.isLong && paramType.isLong)
// return true
// if(argType.isFloat && paramType.isFloat)
// return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
@@ -46,6 +46,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
return when {
rightDt.isByteOrBool -> translateIfByte(stmt, jumpAfterIf)
rightDt.isWord || rightDt.isPointer -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isLong -> translateIfLong(stmt, compareCond, jumpAfterIf)
rightDt.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
else -> throw AssemblyError("weird dt")
}
@@ -575,6 +576,29 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateIfLong(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val constValue = condition.right.asConstInteger()
if(constValue==0) {
// optimized comparisons with zero
return when (condition.operator) {
"==" -> longEqualsZero(condition.left, false, jumpAfterIf, stmt)
"!=" -> longEqualsZero(condition.left, true, jumpAfterIf, stmt)
"<" -> longLessZero(condition.left, false, jumpAfterIf, stmt)
"<=" -> longLessZero(condition.left, true, jumpAfterIf, stmt)
">" -> longGreaterZero(condition.left, false, jumpAfterIf, stmt)
">=" -> longGreaterZero(condition.left, true, jumpAfterIf, stmt)
else -> throw AssemblyError("expected comparison operator")
}
}
return when (condition.operator) {
"==" -> longEqualsValue(condition.left, condition.right, false, jumpAfterIf, stmt)
"!=" -> longEqualsValue(condition.left, condition.right, true, jumpAfterIf, stmt)
"<", "<=", ">", ">=" -> compareLongValues(condition.left, condition.operator, condition.right, jumpAfterIf, stmt)
else -> throw AssemblyError("expected comparison operator")
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -606,7 +630,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordGreaterEqualsZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word>=0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
@@ -619,7 +643,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordLessZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word<0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
@@ -894,23 +918,31 @@ _jump jmp (${target.asmLabel})
}
private fun loadAndCmp0MSB(value: PtExpression) {
private fun loadAndCmp0MSB(value: PtExpression, long: Boolean) {
when(value) {
is PtArrayIndexer -> {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varname = asmgen.asmVariableName(value.variable!!)
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords)
if(value.splitWords) {
require(!long)
asmgen.out(" lda ${varname}_msb,y")
}
else if(long)
asmgen.out(" lda $varname+3,y")
else
asmgen.out(" lda $varname+1,y")
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(value)
asmgen.out(" lda $varname+1")
if(long)
asmgen.out(" lda $varname+3")
else
asmgen.out(" lda $varname+1")
}
is PtAddressOf -> {
require(!long) {"addresses must still be words not longs"}
if(value.isFromArrayElement) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
@@ -923,8 +955,14 @@ _jump jmp (${target.asmLabel})
}
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
if(long) {
// note: clobbers R14+R15
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
asmgen.out(" lda cx16.r14+3")
} else {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
}
}
}
}
@@ -1226,6 +1264,111 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsZero(value: PtExpression, notEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(value is PtIdentifier) {
val varname = asmgen.asmVariableName(value)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
asmgen.out("""
lda cx16.r14
ora cx16.r14+1
ora cx16.r14+2
ora cx16.r14+3
sta P8ZP_SCRATCH_REG""")
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG ; restore flags")
}
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
checkLongLeZeroIntoCarry(value)
if (jump != null)
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
}
}
private fun longGreaterZero(value: PtExpression, greaterEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(greaterEquals) {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
checkLongGtZeroIntoCarry(value)
if (jump != null)
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun checkLongGtZeroIntoCarry(value: PtExpression) {
if(value is PtIdentifier) {
val varname = asmgen.asmVariableName(value)
asmgen.out("""
lda #<$varname
ldy #>$varname
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
asmgen.out(" jsr prog8_lib.compare_long_gt_0")
} else {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
asmgen.out("""
lda #<cx16.r14
ldy #>cx16.r14
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
asmgen.out(" jsr prog8_lib.compare_long_gt_0")
}
}
private fun checkLongLeZeroIntoCarry(value: PtExpression) {
if(value is PtIdentifier) {
val varname = asmgen.asmVariableName(value)
asmgen.out("""
lda #<$varname
ldy #>$varname
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
asmgen.out(" jsr prog8_lib.compare_long_le_0")
} else {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, value.type.isSigned)
asmgen.out("""
lda #<cx16.r14
ldy #>cx16.r14
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
asmgen.out(" jsr prog8_lib.compare_long_le_0")
}
}
private fun wordEqualsZero(value: PtExpression, notEquals: Boolean, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for (u)word == 0
@@ -1303,6 +1446,141 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsValue(
left: PtExpression,
right: PtExpression,
notEquals: Boolean,
jump: PtJump?,
stmt: PtIfElse
) {
val constRight = right.asConstInteger()
val variableRight = (right as? PtIdentifier)?.name
if(left is PtIdentifier) {
val leftvar = asmgen.asmVariableName(left)
if(constRight!=null) {
val hex = constRight.toLongHex()
asmgen.out("""
lda $leftvar
cmp #$${hex.substring(6,8)}
bne +
lda $leftvar+1
cmp #$${hex.substring(4, 6)}
bne +
lda $leftvar+2
cmp #$${hex.substring(2, 4)}
bne +
lda $leftvar+3
cmp #$${hex.take(2)}
+""")
} else if(variableRight!=null) {
require(right.type.isLong)
asmgen.out("""
lda #<$leftvar
ldy #>$leftvar
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$variableRight
ldy #>$variableRight
jsr prog8_lib.long_not_equals""")
}
}
else
{
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
asmgen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
if(constRight!=null) {
val hex = constRight.toLongHex()
asmgen.out("""
lda cx16.r14
cmp #$${hex.substring(6,8)}
bne +
lda cx16.r14+1
cmp #$${hex.substring(4, 6)}
bne +
lda cx16.r14+2
cmp #$${hex.substring(2, 4)}
bne +
lda cx16.r14+3
cmp #$${hex.take(2)}
+""")
} else if(variableRight!=null) {
require(right.type.isLong)
asmgen.out("""
lda cx16.r14
cmp $variableRight
bne +
lda cx16.r14+1
cmp $variableRight+1
bne +
lda cx16.r14+2
cmp $variableRight+2
bne +
lda cx16.r14+3
cmp $variableRight+3
+""")
} else {
TODO("long == value expression ${right.position}")
}
}
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun compareLongValues(
left: PtExpression,
operator: String,
right: PtExpression,
jump: PtJump?,
stmt: PtIfElse
) {
// this comparison is not part of an expression but part of an if statement, there's no need to save the previous values of the temp registers
if(operator=="<" || operator ==">=") {
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R14R15, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R12R13, left.type.isSigned)
} else {
// flip operands
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R12R13, left.type.isSigned)
}
asmgen.out("""
sec
lda cx16.r12
sbc cx16.r14
lda cx16.r12+1
sbc cx16.r14+1
lda cx16.r12+2
sbc cx16.r14+2
lda cx16.r12+3
sbc cx16.r14+3""")
when(operator) {
"<", ">" -> {
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
}
">=", "<=" -> {
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
}
else -> throw RuntimeException("invalid operator $operator")
}
}
private fun wordEqualsValue(
left: PtExpression,
right: PtExpression,
@@ -1709,7 +1987,7 @@ _jump jmp (${target.asmLabel})
if(notEquals)
translateAYNotEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
else
translateAYNotEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
translateAYEquals("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
}
}
}
@@ -1889,4 +2167,5 @@ _jump jmp (${target.asmLabel})
else -> throw AssemblyError("expected comparison operator")
}
}
}
}
@@ -15,32 +15,27 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
val falseLabel = asmgen.makeLabel("ifexpr_false")
val endLabel = asmgen.makeLabel("ifexpr_end")
evalIfExpressionConditonAndBranchWhenFalse(expr.condition, falseLabel)
when {
expr.type.isByteOrBool -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
expr.type.isFloat -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
asmgen.out(endLabel)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt")
if(expr.type.isByteOrBool) {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
} else if(expr.type.isFloat) {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
asmgen.out(endLabel)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
} else {
asmgen.assignExpressionTo(expr.truevalue, target)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionTo(expr.falsevalue, target)
asmgen.out(endLabel)
}
}
@@ -114,6 +109,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
return when {
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
rightDt.isLong -> translateIfExpressionLongConditionBranch(condition, falseLabel)
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
@@ -200,6 +196,36 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionLongConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
// TODO can we reuse this whole thing from IfElse ?
val constValue = condition.right.asConstInteger()
if(constValue!=null) {
if (constValue == 0) {
when (condition.operator) {
"==" -> return translateLongExprIsZero(condition.left, falseLabel)
"!=" -> return translateLongExprIsNotZero(condition.left, falseLabel)
}
}
if (constValue != 0) {
when (condition.operator) {
"==" -> return translateLongExprEqualsNumber(condition.left, constValue, falseLabel)
"!=" -> return translateLongExprNotEqualsNumber(condition.left, constValue, falseLabel)
}
}
}
val variable = condition.right as? PtIdentifier
if(variable!=null) {
when (condition.operator) {
"==" -> return translateLongExprEqualsVariable(condition.left, variable, falseLabel)
"!=" -> return translateLongExprNotEqualsVariable(condition.left, variable, falseLabel)
}
}
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
asmgen.assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
val constValue = (condition.right as? PtNumber)?.number
if(constValue==0.0) {
@@ -339,6 +365,91 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
// if L==number
// TODO reuse code from ifElse?
val hex = number.toLongHex()
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp #$${hex.substring(6, 8)}
bne $falseLabel
lda $varname+1
cmp #$${hex.substring(4, 6)}
bne $falseLabel
lda $varname+2
cmp #$${hex.substring(2, 4)}
bne $falseLabel
lda $varname+3
cmp #$${hex.take(2)}
bne $falseLabel""")
} else {
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
cmp #$${hex.substring(6, 8)}
bne $falseLabel
lda cx16.r14+1
cmp #$${hex.substring(4, 6)}
bne $falseLabel
lda cx16.r14+2
cmp #$${hex.substring(2, 4)}
bne $falseLabel
lda cx16.r14+3
cmp #$${hex.take(2)}
bne $falseLabel""")
}
}
private fun translateLongExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L==variable
// TODO reuse code from ifElse?
val varname2 = asmgen.asmVariableName(variable)
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp $varname2
bne $falseLabel
lda $varname+1
cmp $varname2+1
bne $falseLabel
lda $varname+2
cmp $varname2+2
bne $falseLabel
lda $varname+3
cmp $varname2+3
bne $falseLabel""")
} else {
// TODO cannot easily preserve R14:R15 on stack because we need the status flags of the comparison in between...
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
cmp $varname2
bne $falseLabel
lda cx16.r14+1
cmp $varname2+1
bne $falseLabel
lda cx16.r14+2
cmp $varname2+2
bne $falseLabel
lda cx16.r14+3
cmp $varname2+3
bne $falseLabel""")
}
}
private fun translateLongExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
TODO("if expression LONG != number ${expr.position}")
}
private fun translateLongExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L!=variable
TODO("if expression LONG != variable ${expr.position}")
}
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if w!=0
// TODO reuse code from ifElse?
@@ -369,6 +480,56 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprIsZero(expr: PtExpression, falseLabel: String) {
// if L==0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
bne $falseLabel""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
ora cx16.r14+1
ora cx16.r14+2
ora cx16.r14+3
sta P8ZP_SCRATCH_REG""")
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG | bne $falseLabel")
}
}
private fun translateLongExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if L!=0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
beq $falseLabel""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
ora cx16.r14+1
ora cx16.r14+2
ora cx16.r14+3
sta P8ZP_SCRATCH_REG""")
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG | beq $falseLabel")
}
}
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
// optimized code for byte comparisons with 0
@@ -210,7 +210,7 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("prog8_slabs\t.block")
asmgen.out("$StMemorySlabBlockName\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
@@ -377,6 +377,7 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
@@ -411,29 +412,22 @@ internal class ProgramAndVarsGen(
asmgen.out("; struct types")
symboltable.allStructInstances.distinctBy { it.structName }.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${it.structName} .struct $structargs\n")
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first
val varname = "f${index}"
val type = when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isFloat -> ".byte" // TODO check that float bytes are passed as an array parameter
else -> throw AssemblyError("weird dt")
}
val type = asmTypeString(dt)
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
}
asmgen.out(" .endstruct\n")
}
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
val (instancesNoInit, instances) = symboltable.allStructInstances().partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n")
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
@@ -445,11 +439,17 @@ internal class ProgramAndVarsGen(
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") }
asmgen.out("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
}
@@ -564,6 +564,7 @@ internal class ProgramAndVarsGen(
else when(dt) {
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.LONG -> asmgen.out("$name .sint ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -774,6 +775,7 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> asmgen.out("${variable.name}\t.char ?")
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ?")
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
alignVar(variable.align)
@@ -788,7 +790,7 @@ internal class ProgramAndVarsGen(
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
TODO("pointers are not supported yet")
TODO("pointers are not supported yet for uninitialized array ${variable.astNode?.position}")
}
else -> {
throw AssemblyError("weird dt")
@@ -822,6 +824,7 @@ internal class ProgramAndVarsGen(
// dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
// dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
// dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
// dt.isLong -> asmgen.out("${variable.name}\t.dint $initialValue")
// dt.isFloat -> {
// if(initialValue==0) {
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
@@ -866,7 +869,7 @@ internal class ProgramAndVarsGen(
}
}
dt.isSplitWordArray -> {
if(dt.elementType().isUnsignedWord) {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
@@ -898,6 +901,16 @@ internal class ProgramAndVarsGen(
asmgen.out(" .sint " + chunk.joinToString())
}
}
dt.isLongArray -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.dint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .dint " + chunk.joinToString())
}
}
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
@@ -914,7 +927,7 @@ internal class ProgramAndVarsGen(
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null))
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
}
@@ -924,7 +937,7 @@ internal class ProgramAndVarsGen(
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.sortedBy { it.name }.forEach {
if(it.dt==BaseDataType.FLOAT)
if(it.dt.isFloat)
asmgen.out(" ${it.name} = ${it.value}")
else
asmgen.out(" ${it.name} = ${it.value.toHex()}")
@@ -972,7 +985,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
@@ -984,8 +997,15 @@ internal class ProgramAndVarsGen(
else
asmgen.asmSymbolName(addrOfSymbol)
}
else
else if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
}
}
else -> throw AssemblyError("invalid dt")
}
@@ -1013,7 +1033,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isArray && dt.elementType().isSignedWord -> array.map {
dt.isSignedWordArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@@ -1021,6 +1041,14 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
dt.isLongArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toLongHex()
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -8,6 +8,7 @@ import prog8.code.core.DataType
import prog8.code.core.RegisterOrPair
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.codegen.cpu6502.AsmGen6502Internal
//
@@ -42,6 +43,12 @@ internal class AnyExprAsmGen(
}
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
expr.type.isLong -> {
require(expr.left.type.isLong && expr.right.type.isLong) {
"both operands must be longs"
}
throw AssemblyError("expression should have been handled otherwise: long ${expr.operator} at ${expr.position}")
}
expr.type.isFloat -> {
require(expr.left.type.isFloat && expr.right.type.isFloat) {
"both operands must be floats"
@@ -179,13 +186,13 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
else -> TODO("float expression operator ${expr.operator}")
else -> TODO("float expression operator ${expr.operator} ${expr.position}")
}
}
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
C64Target.NAME, PETTarget.NAME -> {
// C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK)
// otherwise the result of certain floating point operations such as FDIVT will be wrong.
// see https://www.c64-wiki.com/wiki/CONUPK
@@ -10,8 +10,8 @@ internal enum class TargetStorageKind {
ARRAY,
MEMORY,
REGISTER,
POINTER, // wherever the pointer variable points to
VOID // assign nothing - used in multi-value assigns for void placeholders
POINTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
}
internal enum class SourceStorageKind {
@@ -43,7 +43,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
variableAsmName!!
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
TODO("asmVarname for array with pointer $position")
asmgen.asmVariableName(array.variable!!)
}
}
@@ -114,25 +114,17 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.FAC2 -> {
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
}
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
in Cx16VirtualRegisters -> {
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
in CombinedLongRegisters -> {
val dt = if(signed) DataType.LONG
else
TODO("unsigned long $pos")
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
else -> throw AssemblyError("weird register $registers")
}
}
File diff suppressed because it is too large. Load diff
@@ -4,6 +4,7 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.toLongHex
internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
@@ -106,13 +107,24 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(target.asmVarname, operator, value.asmVarname)
SourceStorageKind.EXPRESSION -> inplacemodificationLongWithExpression(target.asmVarname, operator, value.expression!!)
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(target.asmVarname, operator, regName(value))
SourceStorageKind.ARRAY -> TODO("inplace modify long with array value ${target.position}")
SourceStorageKind.MEMORY -> TODO("inplace modify long with memread value ${target.position}")
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt().toDouble())
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(target.asmVarname, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(target.asmVarname, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -152,18 +164,18 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
is PtIdentifier -> {
val pointer = memory.address as PtIdentifier
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationBytePointerWithLiteralval(pointer, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationBytePointerWithLiteralval(pointer, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationBytePointerWithVariable(pointer, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationBytePointerWithVariable(pointer, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into pointer")
SourceStorageKind.ARRAY -> inplacemodificationBytePointerWithValue(pointer, operator, value.array!!)
SourceStorageKind.LITERALBOOLEAN -> ptrgen.inplacemodificationBytePointerWithLiteralval(pointer, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> ptrgen.inplacemodificationBytePointerWithLiteralval(pointer, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> ptrgen.inplacemodificationBytePointerWithVariable(pointer, operator, value.asmVarname)
SourceStorageKind.REGISTER -> ptrgen.inplacemodificationBytePointerWithVariable(pointer, operator, regName(value))
SourceStorageKind.MEMORY -> ptrgen. inplacemodificationBytePointerWithValue(pointer, operator, value.memory!!)
SourceStorageKind.ARRAY -> ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationBytePointerWithValue(pointer, operator, value.expression)
ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.expression)
} else {
inplacemodificationBytePointerWithValue(pointer, operator, value.expression!!)
ptrgen.inplacemodificationBytePointerWithValue(pointer, operator, value.expression!!)
}
}
}
@@ -291,13 +303,32 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
TODO("inplace long modification ${target.position}")
} else {
TODO("inplace long modification ${target.position}")
}
}
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.boolean!!.asInt().toDouble())
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float array")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(targetVarName, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -488,7 +519,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(tempvar, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -523,6 +554,502 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
// it's not an expression so no need to preserve R14-R15
if(value.type.isSigned) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
} else {
if(value.type isAssignableTo DataType.LONG)
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
else
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, false)
}
inplacemodificationLongWithVariable(targetVar, operator, "cx16.r14")
}
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
when(operator) {
"+" -> {
asmgen.out("""
clc
lda $targetVar
adc $sourceVar
sta $targetVar
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3""")
}
"-" -> {
asmgen.out("""
sec
lda $targetVar
sbc $sourceVar
sta $targetVar
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3""")
}
"<<" -> {
asmgen.out("""
ldy $sourceVar
- asl $targetVar
rol $targetVar+1
rol $targetVar+2
rol $targetVar+3
dey
bne -""")
}
">>" -> {
asmgen.out("""
ldy $sourceVar
- lda $targetVar+3
asl a ; save sign bit
ror $targetVar+3
ror $targetVar+2
ror $targetVar+1
ror $targetVar
dey
bne -""")
}
"|" -> {
asmgen.out("""
lda $targetVar
ora $sourceVar
sta $targetVar
lda $targetVar+1
ora $sourceVar+1
sta $targetVar+1
lda $targetVar+2
ora $sourceVar+2
sta $targetVar+2
lda $targetVar+3
ora $sourceVar+3
sta $targetVar+3""")
}
"&" -> {
asmgen.out("""
lda $targetVar
and $sourceVar
sta $targetVar
lda $targetVar+1
and $sourceVar+1
sta $targetVar+1
lda $targetVar+2
and $sourceVar+2
sta $targetVar+2
lda $targetVar+3
and $sourceVar+3
sta $targetVar+3""")
}
"^" -> {
asmgen.out("""
lda $targetVar
eor $sourceVar
sta $targetVar
lda $targetVar+1
eor $sourceVar+1
sta $targetVar+1
lda $targetVar+2
eor $sourceVar+2
sta $targetVar+2
lda $targetVar+3
eor $sourceVar+3
sta $targetVar+3""")
}
else -> {
TODO("in-place modify LONG with variable")
}
}
}
internal fun inplacemodificationLongWithLiteralval(variable: String, operator: String, value: Int) {
fun inplaceLongShiftLeft() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
asl $variable
rol $variable+1
rol $variable+2
rol $variable+3""")
}
}
value in 3..7 -> {
asmgen.out("""
ldy #$value
- asl $variable
rol $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
value == 8 -> {
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable""")
}
value in 9..15 -> {
val shift = value - 8
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable""")
if(shift==1) {
asmgen.out("""
asl $variable+1
rol $variable+2
rol $variable+3""")
} else {
asmgen.out("""
ldy #$shift
- asl $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
}
value == 16 -> {
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1""")
}
value in 17..23 -> {
val shift = value-16
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1""")
if(shift==1) {
asmgen.out(" asl $variable+2 | rol $variable+3")
} else {
asmgen.out("""
ldy #$shift
- asl $variable+2
rol $variable+3
dey
bne -""")
}
}
value == 24 -> {
asmgen.out("""
lda $variable
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
value <= 31 -> {
val shift = value-24
if(shift==1) {
asmgen.out(" lda $variable | asl a")
} else {
asmgen.out("""
lda $variable
ldy #$shift
- asl a
dey
bne -""")
}
asmgen.out("""
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
fun inplaceLongShiftRight() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable""")
}
}
value==8 -> {
asmgen.out("""
lda $variable+1
sta $variable
lda $variable+2
sta $variable+1
lda $variable+3
sta $variable+2
bpl +
lda #255
bne ++
+ lda #0
+ sta $variable+3""")
}
value==16-> {
asmgen.out("""
lda $variable+2
sta $variable
lda $variable+3
sta $variable+1
bpl +
lda #255
bne ++
+ lda #0
+ sta $variable+2
sta $variable+3""")
}
value==24 -> {
asmgen.out("""
lda $variable+3
sta $variable
bpl +
lda #255
bne ++
+ lda #0
+ sta $variable+1
sta $variable+2
sta $variable+3""")
}
value <= 31 -> {
asmgen.out("""
ldy #$value
- lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable
dey
bne -""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
when(operator) {
"+" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
inc $variable
bne +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
clc
lda $variable
adc #$value
sta $variable
bcc +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
clc
lda $variable
adc #<$value
sta $variable
lda $variable+1
adc #>$value
sta $variable+1
bcc +
inc $variable+2
bne +
inc $variable+3
+""")
} else {
val hex = value.toLongHex()
asmgen.out("""
clc
lda $variable
adc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
adc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
adc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
adc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"-" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
lda $variable
bne +++
lda $variable+1
bne ++
lda $variable+2
bne +
dec $variable+3
+ dec $variable+2
+ dec $variable+1
+ dec $variable""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
sec
lda $variable
sbc #$value
sta $variable
bcs +++
lda $variable+1
bne ++
lda $variable+2
bne +
dec $variable+3
+ dec $variable+2
+ dec $variable+1
+""")
} else {
val hex = value.toLongHex()
asmgen.out("""
sec
lda $variable
sbc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
sbc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
sbc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
sbc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"<<" -> if (value > 0) inplaceLongShiftLeft()
">>" -> if (value > 0) inplaceLongShiftRight()
"|" -> {
val hex = value.toLongHex()
asmgen.out("""
lda $variable
ora #$${hex.substring(6,8)}
sta $variable
lda $variable+1
ora #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
ora #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
ora #$${hex.take(2)}
sta $variable+3""")
}
"&" -> {
val hex = value.toLongHex()
asmgen.out("""
lda $variable
and #$${hex.substring(6,8)}
sta $variable
lda $variable+1
and #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
and #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
and #$${hex.take(2)}
sta $variable+3""")
}
"^" -> {
val hex = value.toLongHex()
asmgen.out("""
lda $variable
eor #$${hex.substring(6,8)}
sta $variable
lda $variable+1
eor #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
eor #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
eor #$${hex.take(2)}
sta $variable+3""")
}
else -> {
TODO("inplace long $operator $value")
}
}
}
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
val arrayVar = array.variable
if(arrayVar==null) {
@@ -912,188 +1439,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return false
}
private fun inplacemodificationBytePointerWithValue(pointervar: PtIdentifier, operator: String, value: PtExpression) {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE)
inplacemodificationBytePointerWithVariable(pointervar, operator, "P8ZP_SCRATCH_B1")
}
private fun inplacemodificationBytePointerWithVariable(pointervar: PtIdentifier, operator: String, otherName: String) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
"+" -> asmgen.out(" clc | adc $otherName")
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl a
dey
bne -
+""")
}
">>" -> {
asmgen.out("""
ldy $otherName
beq +
- lsr a
dey
bne -
+""")
}
"&" -> asmgen.out(" and $otherName")
"|" -> asmgen.out(" ora $otherName")
"^" -> asmgen.out(" eor $otherName")
"==" -> {
asmgen.out("""
cmp $otherName
beq +
lda #0
beq ++
+ lda #1
+""")
}
"!=" -> {
asmgen.out("""
cmp $otherName
bne +
lda #0
beq ++
+ lda #1
+""")
}
// pretty uncommon, who's going to assign a comparison boolean expression to a pointer?
"<", "<=", ">", ">=" -> TODO("byte-var-to-pointer comparisons")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
private fun inplacemodificationBytePointerWithLiteralval(pointervar: PtIdentifier, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
"+" -> {
if(value==1) {
if(asmgen.options.romable) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" inc a")
else
asmgen.out(" clc | adc #1")
asmgen.storeAIntoZpPointerVar(sourceName, false)
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out($$"+\tinc $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" clc | adc #$value")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
}
"-" -> {
if(value==1) {
if(asmgen.options.romable) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" dec a")
else
asmgen.out(" sec | sbc #1")
asmgen.storeAIntoZpPointerVar(sourceName, false)
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out($$"+\tdec $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sec | sbc #$value")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
}
"*" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr prog8_math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"/" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm | tya")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"%" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
if (value > 0) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
repeat(value) { asmgen.out(" asl a") }
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
}
">>" -> {
if (value > 0) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
repeat(value) { asmgen.out(" lsr a") }
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
}
"&" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and #$value")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"|"-> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" ora #$value")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"^" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" eor #$value")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"==" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out("""
cmp #$value
beq +
lda #0
beq ++
+ lda #1
+""")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"!=" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out("""
cmp #$value
bne +
lda #0
beq ++
+ lda #1
+""")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
// pretty uncommon, who's going to assign a comparison boolean expression to a pointer?:
"<", "<=", ">", ">=" -> TODO("byte-litval-to-pointer comparisons")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
private fun inplacemodificationByteWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
require(dt.isByteOrBool)
if(!value.isSimple()) {
@@ -1183,7 +1528,7 @@ $shortcutLabel:""")
}
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
if(operator in "+-" && variable in arrayOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
if(operator in "+-" && variable in setOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
if(operator=="+")
asmgen.out(" inc a")
@@ -1851,7 +2196,7 @@ $shortcutLabel:""")
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
} else {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware mul
asmgen.out("""
lda $lsb
@@ -1862,9 +2207,10 @@ $shortcutLabel:""")
ldy #>$value
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $lsb
sty $msb""")
}
else
asmgen.out("""
lda $lsb
@@ -2349,9 +2695,10 @@ $shortcutLabel:""")
ldy $name+1
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
} else {
if(valueDt.isUnsignedByte) {
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
@@ -2494,7 +2841,7 @@ $shortcutLabel:""")
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
"*" -> {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
lda $name
@@ -2505,9 +2852,10 @@ $shortcutLabel:""")
ldy $otherName+1
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
lda $otherName
@@ -2698,7 +3046,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
require(dt.isWord)
fun multiplyVarByWordInAX() {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
@@ -2707,9 +3055,10 @@ $shortcutLabel:""")
ldx $name+1
sta cx16.r0
stx cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
sta prog8_math.multiply_words.multiplier
@@ -3003,7 +3352,7 @@ $shortcutLabel:""")
if(value is PtNumber && value.number<=255) {
TODO("shift a word var by ${value.number}")
} else {
throw AssemblyError("shift by a word value not supported, max is a byte")
throw AssemblyError("bit shift value can not be larger than a byte")
}
}
"&" -> {
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -12,12 +12,14 @@ internal object DummyMemsizer : IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
@@ -64,8 +66,8 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
infos.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
+1
View File
@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -9,7 +9,7 @@ import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
class ExperiCodeGen: ICodeGeneratorBackend {
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
@@ -19,7 +19,7 @@ class ExperiCodeGen: ICodeGeneratorBackend {
// you could write a code generator directly on the PtProgram AST,
// but you can also use the Intermediate Representation to build a codegen on:
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
@@ -24,7 +24,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(extsub!=null) {
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
if (funcCall.multipleResultFpRegs.isNotEmpty())
TODO("deal with (multiple?) FP return registers")
TODO("deal with (multiple?) FP return registers ${assignment.position}")
if (extsub.returns.size == assignmentTargets.size) {
// Targets and values match. Assign all the things. Skip 'void' targets.
extsub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
@@ -76,6 +76,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
in CombinedLongRegisters -> {
require(returns.type.isLong)
val startreg = returns.register.registerOrPair!!.startregname()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1=regNum, labelSymbol = "cx16.${startreg}"), null)
}
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum), null)
null -> if(returns.register.statusflag!=null)
@@ -118,6 +123,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
if(augAssign.target.type.isString)
throw AssemblyError("cannot assign to str type ${augAssign.position}")
// augmented assignment always has just a single target
if (augAssign.target.children.single() is PtIrRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
@@ -137,63 +145,78 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val oldvalueReg = codeGen.registers.next(targetDt)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
inplaceInstrs += operandTr.chunks
}
if(targetDt== IRDataType.FLOAT) {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
if((augAssign.operator=="+=" || augAssign.operator=="-=") && value.asConstInteger()==1 || value.asConstInteger()==2) {
// INC/DEC optimization instead of ADD/SUB
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC
repeat(value.asConstInteger()!!) {
addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null)
}
} else {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
// note: the instructions to load the value will be placed after the LOADFIELD instruction so that later optimizations about what modification is actually done, are easier
}
if(targetDt== IRDataType.FLOAT) {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
} else {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> {
val opc = if (signed) Opcode.ASRN else Opcode.LSRN
addInstr(inplaceInstrs, IRInstruction(opc, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
}
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
}
@@ -222,6 +245,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return chunks
}
private fun loadfield(
inplaceInstrs: MutableList<IRCodeChunkBase>,
addressReg: Int,
fieldOffset: UByte,
targetDt: IRDataType,
oldvalueReg: Int
) {
if (targetDt == IRDataType.FLOAT) {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg),
null
)
} else {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg),
null
)
}
}
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
@@ -414,11 +473,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
val targetIdent = assignment.target.identifier
val targetMemory = assignment.target.memory
val targetArray = assignment.target.array
val targetPointerDeref = assignment.target.pointerDeref
if(assignment.target.type.isString) {
// assigning array and string values is done via an explicit memcopy/stringcopy function calls.
throw AssemblyError("cannot assign to str type ${assignment.position}")
}
val valueDt = irType(assignment.value.type)
val targetDt = irType(assignment.target.type)
val result = mutableListOf<IRCodeChunkBase>()
@@ -459,98 +518,92 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
if(targetIdent!=null) {
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
} else {
if (targetDt == IRDataType.FLOAT) {
require(valueFpRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
}
else {
require(valueRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
}
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
else if(targetArray!=null) {
val eltSize = codeGen.program.memsizer.memorySize(targetArray.type, null)
val variable = targetArray.variable
if(variable==null)
translateRegularAssignPointerIndexed(result, targetArray.pointerderef!!, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
else if(variable.type.isPointer)
assignToIndexedSimplePointer(result, variable, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
else
translateRegularAssignArrayIndexed(result, variable.name, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
return result
}
else if(targetMemory!=null) {
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
if(zero) {
if(targetMemory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
}
}
} else {
val constAddress = targetMemory.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
}
return result
with(assignment.target) {
when {
identifier != null -> {
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = identifier!!.name)
} else {
if (targetDt == IRDataType.FLOAT) {
require(valueFpRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = identifier!!.name)
}
else {
require(valueRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = identifier!!.name)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
expressionEval.translateExpression(offsetTypecast.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
memory != null -> {
require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"}
if(zero) {
if(memory!!.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (memory!!.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(memory!!.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
}
}
} else {
val constAddress = memory!!.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = memory!!.address as? PtBinaryExpression
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..65535) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
}
return result
}
}
}
val tr = expressionEval.translateExpression(memory!!.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
return result
}
return result
}
array != null -> {
val eltSize = codeGen.program.memsizer.memorySize(array!!.type, null)
val variable = array!!.variable
if(variable==null)
translateRegularAssignPointerIndexed(result, array!!.pointerderef!!, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
else if(variable.type.isPointer)
assignToIndexedSimplePointer(result, variable, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
else
translateRegularAssignArrayIndexed(result, variable.name, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
return result
}
pointerDeref != null -> {
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, pointerDeref!!)
val actualValueReg = if(pointerDeref!!.type.isFloat) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, addressReg, offset, pointerDeref!!.type, zero, actualValueReg)
return result
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
return result
}
else -> {
throw AssemblyError("weird assigntarget")
}
}
return result
}
else if(targetPointerDeref!=null) {
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
val actualValueReg = if(targetPointerDeref.type.isFloat) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, addressReg, offset, targetPointerDeref.type, zero, actualValueReg)
return result
}
else
throw AssemblyError("weird assigntarget")
}
private fun assignToIndexedSimplePointer(
@@ -647,9 +700,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val chunk = IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
val lsbmsbReg = codeGen.registers.next(IRDataType.BYTE)
it += IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = lsbmsbReg, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = lsbmsbReg, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
else
@@ -662,9 +715,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
result += IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
val lsbmsbReg = codeGen.registers.next(IRDataType.BYTE)
it += IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = lsbmsbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = lsbmsbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
}
else
@@ -914,7 +967,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun operatorDivideInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? {
if(array!=null) {
TODO("/ in array")
TODO("/ in array ${array.position}")
}
if(constAddress==null && memory!=null)
return null // TODO("optimized memory in-place /"")
@@ -1054,14 +1107,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(constAddress==null && memory!=null)
return null // TODO("optimized memory in-place -"")
val constValue = (operand as? PtNumber)?.number
val result = mutableListOf<IRCodeChunkBase>()
if(vmDt==IRDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
else
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
, null)
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
}
else {
val tr = expressionEval.translateExpression(operand)
@@ -1069,16 +1122,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, address = constAddress)
else
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol)
, null)
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol), null)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
else
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
, null)
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
}
else {
val tr = expressionEval.translateExpression(operand)
@@ -1086,8 +1137,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, address = constAddress)
else
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
, null)
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
}
}
return result
@@ -1153,9 +1203,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return null // TODO("optimized memory in-place +"")
val result = mutableListOf<IRCodeChunkBase>()
val constValue = (operand as? PtNumber)?.number
if(vmDt==IRDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if (constAddress != null)
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
else
@@ -1170,7 +1221,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
IRInstruction(Opcode.ADDM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol) , null)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if (constAddress != null)
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
else
@@ -1259,11 +1310,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} else {
val shiftTr = expressionEval.translateExpression(operand)
addToResult(result, shiftTr, shiftTr.resultReg, -1)
var shiftReg = shiftTr.resultReg
if(vmDt==IRDataType.WORD && shiftTr.dt==IRDataType.BYTE) {
shiftReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=shiftReg, reg2=shiftTr.resultReg), null)
}
val shiftReg = shiftTr.resultReg
val opc = if (signed) Opcode.ASRNM else Opcode.LSRNM
val ins = if(constAddress!=null)
IRInstruction(opc, vmDt, reg1 = shiftReg, address = constAddress)
@@ -1321,11 +1368,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} else {
val shiftTr = expressionEval.translateExpression(operand)
addToResult(result, shiftTr, shiftTr.resultReg, -1)
var shiftReg = shiftTr.resultReg
if(vmDt==IRDataType.WORD && shiftTr.dt==IRDataType.BYTE) {
shiftReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=shiftReg, reg2=shiftTr.resultReg), null)
}
val shiftReg = shiftTr.resultReg
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.LSLNM, vmDt, reg1=shiftReg, address = constAddress)
else
@@ -1380,7 +1423,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun operatorModuloInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? {
if(array!=null) {
TODO("% in array")
TODO("% in array ${array.position}")
}
if(constAddress==null && memory!=null)
return null // TODO("optimized memory in-place %"")
@@ -1,5 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
@@ -12,35 +14,40 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return when(call.name) {
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__long", "sqrt__float" -> funcSqrt(call)
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
"call" -> funcCall(call)
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"msw" -> funcMsw(call)
"lsw" -> funcLsw(call)
"msb" -> funcMsb(call, false)
"msb__long" -> funcMsb(call, true)
"lsb" -> funcLsb(call, false)
"lsb__long" -> funcLsb(call, true)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekl" -> funcPeek(call, IRDataType.LONG)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokebool" -> funcPoke(call, IRDataType.BYTE)
"pokebowl" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokel" -> funcPoke(call, IRDataType.LONG)
"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)
"mklong", "mklong2" -> funcMklong(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
@@ -51,11 +58,44 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"prog8_lib_copylong" -> funcCopyFromPointer1ToPointer2(call, IRDataType.LONG)
"prog8_lib_copyfloat" -> funcCopyFromPointer1ToPointer2(call, IRDataType.FLOAT)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
"swap__byte" -> funcSwap(call)
"swap__word" -> funcSwap(call)
"swap__long" -> funcSwap(call)
"swap__float" -> funcSwap(call)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
private fun funcCopyFromPointer1ToPointer2(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val trSourceAddr = exprGen.translateExpression(call.args[0])
val trTargetAddr = exprGen.translateExpression(call.args[1])
addToResult(result, trSourceAddr, trSourceAddr.resultReg, -1)
addToResult(result, trTargetAddr, trTargetAddr.resultReg, -1)
when(type) {
IRDataType.LONG -> {
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg)
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg)
}
}
IRDataType.FLOAT -> {
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, fpReg1=intermediateReg, reg1=trSourceAddr.resultReg)
it += IRInstruction(Opcode.STOREI, type, fpReg1=intermediateReg, reg1=trTargetAddr.resultReg)
}
}
else -> throw AssemblyError("invalid type $type")
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])
@@ -79,8 +119,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.next(IRDataType.WORD), -1) // TODO actually the result is returned in CPU registers AY...
else {
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
}
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -201,6 +244,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
}
BaseDataType.LONG -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
@@ -213,7 +267,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
val resultReg = codeGen.registers.next(tr.dt)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
@@ -250,6 +304,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
BaseDataType.LONG -> {
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD) // sqrt of a long still produces just a word result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.LONG, reg1=resultReg, reg2=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
BaseDataType.FLOAT -> {
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
@@ -280,6 +342,52 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(IRDataType.LONG)
if(call.args.size==2) {
// mklong2(word, word)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msw is 0, use EXT
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
} else {
val mswTr = exprGen.translateExpression(call.args[0])
addToResult(result, mswTr, mswTr.resultReg, -1)
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
}
} else {
// mklong(msb, b3, b2, lsb)
if((call.args[0] as? PtNumber)?.number == 0.0 && (call.args[1] as? PtNumber)?.number == 0.0 && (call.args[2] as? PtNumber)?.number == 0.0) {
// use EXT.b + EXT.w
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val wordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=wordReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = wordReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
val b2Tr = exprGen.translateExpression(call.args[1])
val b1Tr = exprGen.translateExpression(call.args[2])
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, msbTr, msbTr.resultReg, -1)
addToResult(result, b2Tr, b2Tr.resultReg, -1)
addToResult(result, b1Tr, b1Tr.resultReg, -1)
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val lswReg = codeGen.registers.next(IRDataType.WORD)
val mswReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
}
}
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val type = irType(call.type)
@@ -304,6 +412,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
result += codeGen.makeSyscall(syscall, listOf(
@@ -354,6 +463,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcSwap(call: PtBuiltinFunctionCall): ExpressionCodeResult {
require(call.args[0].type == call.args[1].type)
val result = mutableListOf<IRCodeChunkBase>()
val dt = irType(call.args[0].type)
val t1 = exprGen.translateExpression(call.args[0])
val t2 = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
addToResult(result, t1, -1, t1.resultFpReg)
addToResult(result, t2, -1, t2.resultFpReg)
addInstr(result, IRInstruction(Opcode.SWAP, dt, fpReg1=t1.resultFpReg, fpReg2=t2.resultFpReg), null)
result += assignFpRegisterTo(call.args[0], t1.resultFpReg)
result += assignFpRegisterTo( call.args[1], t2.resultFpReg)
} else {
addToResult(result, t1, t1.resultReg, -1)
addToResult(result, t2, t2.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SWAP, dt, reg1=t1.resultReg, reg2=t2.resultReg), null)
result += assignRegisterTo(call.args[0], t1.resultReg)
result += assignRegisterTo( call.args[1], t2.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
@@ -371,6 +503,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
} else {
if (call.args[0] is PtNumber) {
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
@@ -385,6 +518,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
} else {
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
@@ -481,6 +615,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
}
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
pokeM(result, addressNum, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -490,6 +625,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
}
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
pokeI(result, addressTr.resultReg, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -500,7 +636,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
@@ -508,30 +644,54 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcLsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg), null)
if(fromLong)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, 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 funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg), null)
if(fromLong)
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.WORD, 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 funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val arg = call.args[0]
@@ -610,8 +770,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
if (msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
}
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
}
} else {
@@ -620,8 +784,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
if (msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
}
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
}
}
@@ -673,7 +841,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val offset = eltSize*constIndex + if(msb) 1 else 0
val offset = eltSize*constIndex + if(msb) eltSize-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 = targetVariable.name)
@@ -684,8 +852,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
if(msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
}
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
@@ -694,7 +866,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val offset = eltSize*constIndex + if(msb) 1 else 0
val offset = eltSize*constIndex + if(msb) eltSize-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 = targetVariable.name)
@@ -705,8 +877,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
if(msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
}
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
@@ -729,4 +905,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += codeGen.translateNode(assignment)
return result
}
private fun assignFpRegisterTo(target: PtExpression, fpRegister: Int): IRCodeChunks {
require(target.type.isFloat)
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(false, target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtIrRegister(fpRegister, DataType.FLOAT, target.position))
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result
}
}
@@ -99,7 +99,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
var pointerReg: Int
if(deref.startpointer.type.isStructInstance) {
TODO("translate structinstance deref???")
TODO("translate structinstance deref??? ${deref.position}")
/*
val arrayIndexer = deref.startpointer as? PtArrayIndexer
if(arrayIndexer==null)
@@ -140,7 +140,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = firstField.second.toInt())
if (firstField.first.isPointer) {
// get the address stored in the pointer and use that for the rest of the chain
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
} else {
require(chain.isEmpty())
@@ -406,7 +406,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
if(constOffset in 0..65535) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
@@ -416,18 +416,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val tr = translateExpression(mem.address)
@@ -446,7 +434,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else (it as PtNumber).number.toInt()
}
when {
elementDt.isIntegerOrBool -> {
elementDt.isWordOrByteOrBool -> {
if (elementDt.isByteOrBool) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt.isWord) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
@@ -686,6 +674,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isLong -> {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
@@ -703,7 +695,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.UWORD, BaseDataType.WORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
@@ -719,7 +715,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.UWORD, BaseDataType.WORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
@@ -743,6 +743,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -768,6 +772,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.UWORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -775,6 +783,37 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.LONG -> {
when(valueDt.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> {
// ubyte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.BYTE -> {
// byte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.UWORD, BaseDataType.POINTER -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
BaseDataType.WORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast $valueDt to long ${cast.position}")
}
}
BaseDataType.FLOAT -> {
actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT)
when(valueDt.base) {
@@ -790,6 +829,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
}
BaseDataType.LONG -> {
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
@@ -799,7 +841,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// no further conversion required, pointers are all just uwords
}
BaseDataType.ARRAY_POINTER -> {
TODO("typecast to array of pointers $valueDt -> ${cast.type}")
TODO("typecast to array of pointers $valueDt -> ${cast.type} ${cast.position}")
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
@@ -885,7 +927,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
val assign = PtAssignment(fcall.position)
val target = PtAssignTarget(true, fcall.position)
val target = PtAssignTarget(false, fcall.position)
target.add(PtIdentifier(regname, parameter.type, fcall.position))
assign.add(target)
assign.add(arg)
@@ -906,7 +948,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
fcallArgs = FunctionCallArgs(argRegisters, returnRegSpecs)), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) // TODO datatype void?
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(returnRegSpecs.size==1) {
val returnRegSpec = returnRegSpecs.single()
if (fcall.type.isFloat)
@@ -1137,7 +1179,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
}
else TODO("extsub with banked address got called ${callTarget.name}")
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
}
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
@@ -1715,9 +1757,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
} else if(left.pointerderef!=null) {
TODO("get pointer from deref $left")
TODO("get pointer from deref $left ${left.position}")
} else {
throw AssemblyError("weird arrayindexer $left")
throw AssemblyError("weird arrayindexer $left ${left.position}")
}
val struct = left.type.subType!! as StStruct
val constindex = left.index as? PtNumber
@@ -1799,7 +1841,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// get new pointer from field
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
}
}
@@ -1812,7 +1854,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
require(fieldinfo.first.isPointer)
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
return result to 0u
} else {
@@ -1821,7 +1863,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
}
return result to 0u
@@ -11,7 +11,8 @@ class IRCodeGen(
internal val program: PtProgram,
internal val symbolTable: SymbolTable,
internal val options: CompilationOptions,
internal val errors: IErrorReporter
internal val errors: IErrorReporter,
internal val retainSSA: Boolean
) {
private val expressionEval = ExpressionGen(this)
@@ -48,8 +49,11 @@ class IRCodeGen(
irProg.linkChunks()
irProg.convertAsmChunks()
if(retainSSA)
irProg.splitSSAchunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
val optimizer = IRPeepholeOptimizer(irProg, retainSSA)
optimizer.optimize(options.optimize, errors)
irProg.validate()
@@ -156,6 +160,7 @@ class IRCodeGen(
is IRCodeChunk -> {
val replacement = IRCodeChunk(sub.label, first.next)
replacement.instructions += first.instructions
replacement.appendSrcPositions(first.sourceLinesPositions)
replacement
}
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
@@ -271,7 +276,7 @@ class IRCodeGen(
is PtSub -> throw AssemblyError("nested subroutines should have been flattened $node")
is PtStructDecl -> emptyList()
is PtSubSignature -> emptyList()
else -> TODO("missing codegen for $node")
else -> TODO("missing codegen for $node ${node.position}")
}
val nonEmptyChunks = chunks.filter { it.isNotEmpty() || it.label != null }
@@ -409,6 +414,7 @@ class IRCodeGen(
is IRCodeChunk -> {
val newChunk = IRCodeChunk(label, first.next)
newChunk.instructions += first.instructions
newChunk.appendSrcPositions(first.sourceLinesPositions)
newChunk
}
is IRInlineAsmChunk -> {
@@ -1141,7 +1147,7 @@ class IRCodeGen(
if(identifier!=null && !isIndirectJump(goto))
IRInstruction(branchOpcode, labelSymbol = identifier.name)
else
TODO("JUMP to expression address ${goto.target}")
TODO("JUMP to expression address ${goto.target} ${goto.position}")
}
}
@@ -1859,7 +1865,7 @@ class IRCodeGen(
is PtNop -> { /* nothing */ }
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtAlign -> TODO("ir support for inline %align")
is PtAlign -> TODO("ir support for inline %align ${child.position}")
is PtSub -> {
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
for (subchild in child.children) {
@@ -1911,7 +1917,7 @@ class IRCodeGen(
}
}
is PtStructDecl -> { /* do nothing, should be found in the symbol table */ }
else -> TODO("weird block child node $child")
else -> TODO("weird block child node $child ${child.position}")
}
}
return irBlock
@@ -1975,12 +1981,17 @@ class IRCodeGen(
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
in CombinedLongRegisters -> {
require(paramDt==IRDataType.LONG)
val startreg = registerOrFlag.registerOrPair!!.startregname()
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
}
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
else -> throw AssemblyError("unsupported register arg $registerOrFlag")
}
return chunk
}
@@ -3,12 +3,12 @@ package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.intermediate.*
class IRPeepholeOptimizer(private val irprog: IRProgram) {
class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: Boolean) {
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
if(!optimizationsEnabled)
return optimizeOnlyJoinChunks()
return optimizeOnlyJoinChunks(retainSSA)
peepholeOptimize()
peepholeOptimize(retainSSA)
val remover = IRUnusedCodeRemover(irprog, errors)
var totalRemovals = 0
do {
@@ -22,21 +22,21 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
private fun optimizeOnlyJoinChunks() {
private fun optimizeOnlyJoinChunks(retainSSA: Boolean) {
// this chunk-joining is REQUIRED (optimization or no) to end up with a structurally sound chunk list
irprog.foreachSub { sub ->
joinChunks(sub)
joinChunks(sub, retainSSA)
removeEmptyChunks(sub)
joinChunks(sub)
joinChunks(sub, retainSSA)
}
irprog.linkChunks() // re-link
}
private fun peepholeOptimize() {
private fun peepholeOptimize(retainSSA: Boolean) {
irprog.foreachSub { sub ->
joinChunks(sub)
joinChunks(sub, retainSSA)
removeEmptyChunks(sub)
joinChunks(sub)
joinChunks(sub, retainSSA)
sub.chunks.withIndex().forEach { (index, chunk1) ->
// we don't optimize Inline Asm chunks here.
@@ -55,6 +55,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| cleanupPushPop(chunk1, indexedInstructions)
|| simplifyConstantReturns(chunk1, indexedInstructions)
|| removeNeedlessLoads(chunk1, indexedInstructions)
|| loadfieldsAndStorefields(chunk1, indexedInstructions)
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
} while (changed)
}
}
@@ -151,7 +153,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
private fun joinChunks(sub: IRSubroutine) {
private fun joinChunks(sub: IRSubroutine, retainSSA: Boolean) {
// Subroutine contains a list of chunks. Some can be joined into one.
if(sub.chunks.isEmpty())
@@ -161,10 +163,12 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
val lastInstruction = previous.instructions.lastOrNull()
if(lastInstruction!=null)
return lastInstruction.opcode !in OpcodesThatJump
if(retainSSA) {
// if the previous chunk doesn't end in a SSA branching instruction, flow continues into the next chunk, so they may be joined
val lastInstruction = previous.instructions.lastOrNull()
if (lastInstruction != null)
return lastInstruction.opcode !in OpcodesThatEndSSAblock
}
return true
}
return false
@@ -300,16 +304,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// remove useless RETURN
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
chunk.instructions.removeAt(idx)
changed = true
}
}
// replace subsequent opcodes that jump by just the first
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
chunk.instructions.removeAt(idx)
changed = true
}
@@ -362,7 +366,7 @@ jump p8_label_gen_2
*/
var changed=false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>=2 && ins.opcode in OpcodesThatJump) {
if(idx>=2 && ins.opcode in OpcodesThatBranchUnconditionally) {
val previous = indexedInstructions[idx-1].value
val previous2 = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) {
@@ -400,6 +404,43 @@ jump p8_label_gen_2
chunk.instructions.removeAt(idx)
changed = true
}
if(!changed && idx < chunk.instructions.size-1) {
val nextInstr = chunk.instructions[idx+1]
if(nextInstr.reg1==ins.reg1) {
when (nextInstr.opcode) {
Opcode.INC -> {
// INC after ADD or SUB
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + 1 else ins.immediate!! - 1
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
chunk.instructions.removeAt(idx + 1)
changed = true
}
Opcode.DEC -> {
// DEC after ADD or SUB
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - 1 else ins.immediate!! + 1
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
chunk.instructions.removeAt(idx + 1)
changed = true
}
Opcode.ADD -> {
// ADD after ADD or SUB
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + nextInstr.immediate!! else ins.immediate!! - nextInstr.immediate!!
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
chunk.instructions.removeAt(idx + 1)
changed = true
}
Opcode.SUB -> {
// SUB after ADD or SUB
val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - nextInstr.immediate!! else ins.immediate!! + nextInstr.immediate!!
chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue)
chunk.instructions.removeAt(idx + 1)
changed = true
}
else -> {}
}
}
}
}
Opcode.AND -> {
when (ins.immediate) {
@@ -415,13 +456,19 @@ jump p8_label_gen_2
chunk.instructions.removeAt(idx)
changed = true
}
-1 if ins.type == IRDataType.LONG -> {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
Opcode.OR -> {
if (ins.immediate == 0) {
chunk.instructions.removeAt(idx)
changed = true
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) || (ins.immediate == 65535 && ins.type == IRDataType.WORD)) {
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) ||
(ins.immediate == 65535 && ins.type == IRDataType.WORD) ||
(ins.immediate == -1 && ins.type == IRDataType.LONG)) {
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = ins.immediate)
changed = true
}
@@ -434,6 +481,54 @@ jump p8_label_gen_2
}
else -> {}
}
fun optimizeImmediateLoadAssociative(replacementOpcode: Opcode) {
fun getImmediateLoad(reg: Int): Pair<Int, Int>? {
// look if the given register gets an immediate value 1 or 2 istructions back
// returns (index of load instruction, immediate value) or null.
if(idx>=1) {
val previous = indexedInstructions[idx-1].value
if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null)
return idx-1 to previous.immediate!!
}
if(idx>=2) {
val previous = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null)
return idx - 2 to previous.immediate!!
}
return null
}
if(ins.reg1!=null) {
val immediate1 = getImmediateLoad(ins.reg1!!)
if(immediate1!=null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
chunk.instructions.removeAt(immediate1.first)
changed=true
} else {
val immediate2 = getImmediateLoad(ins.reg2!!)
if (immediate2 != null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
chunk.instructions.removeAt(immediate2.first)
changed=true
}
}
}
}
// try to use immediate arithmetic instruction if possible
when(ins.opcode) {
Opcode.ADDR -> optimizeImmediateLoadAssociative(Opcode.ADD)
Opcode.MULR -> optimizeImmediateLoadAssociative(Opcode.MUL)
Opcode.MULSR -> optimizeImmediateLoadAssociative(Opcode.MULS)
// Opcode.SUBR -> TODO("ir peephole Subr")
// Opcode.DIVR -> TODO("ir peephole Divr")
// Opcode.DIVSR -> TODO("ir peephole Divsr")
// Opcode.MODR -> TODO("ir peephole Modr")
// Opcode.DIVMODR -> TODO("ir peephole DivModr")
else -> {}
}
}
return changed
}
@@ -499,4 +594,22 @@ jump p8_label_gen_2
}
return changed
}
private fun loadfieldsAndStorefields(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.LOADFIELD && ins.immediate==0) {
val loadi = IRInstruction(Opcode.LOADI, ins.type, ins.reg1!!, ins.reg2!!)
chunk.instructions[idx] = loadi
changed = true
}
else if (ins.opcode == Opcode.STOREFIELD && ins.immediate==0) {
val loadi = IRInstruction(Opcode.STOREI, ins.type, ins.reg1!!, ins.reg2!!)
chunk.instructions[idx] = loadi
changed = true
}
}
return changed
}
}
@@ -7,6 +7,7 @@ internal class RegisterPool {
// everything from 99000 onwards is reserved for special purposes:
// 99000 - 99099 : WORD registers for syscall arguments and response value(s)
// 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
private var nextRegister: Int=1
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
@@ -18,6 +19,8 @@ internal class RegisterPool {
registerTypes[i] = IRDataType.WORD
for(i in 99100..99199)
registerTypes[i] = IRDataType.BYTE
for(i in 99200..99299)
registerTypes[i] = IRDataType.LONG
}
fun next(type: IRDataType): Int {
@@ -47,10 +47,16 @@ private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
return if (elt.boolean != null)
IRStArrayElement(elt.boolean, null, null)
else if(elt.number!=null)
IRStArrayElement(null, elt.number, null)
else {
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
IRStArrayElement(null, null, symbol)
}
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
@@ -72,7 +78,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
val target = variable.lookup(it.addressOfSymbol!!) ?:
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
@@ -115,7 +122,6 @@ private fun convert(variable: StMemVar): IRStMemVar {
private fun convert(constant: StConstant): IRStConstant {
val dt = DataType.forDt(constant.dt)
val scopedName = if('.' in constant.name) {
constant.name
} else {
@@ -125,15 +131,15 @@ private fun convert(constant: StConstant): IRStConstant {
constant.name
}
}
return IRStConstant(scopedName, dt, constant.value)
return IRStConstant(scopedName, constant.dt, constant.value)
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
private fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
@@ -142,8 +148,8 @@ private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType,
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?
@@ -1,23 +1,23 @@
package prog8.codegen.vm
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram
class VmCodeGen: ICodeGeneratorBackend {
class VmCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram {
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
+3 -1
View File
@@ -10,12 +10,14 @@ internal object DummyMemsizer : IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
@@ -62,7 +64,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
infos.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
+21 -14
View File
@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
noSysInit = true,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -48,7 +49,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.NOP)
))
irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 1
}
@@ -67,7 +68,7 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val chunks = irProg.chunks()
chunks.size shouldBe 4
@@ -101,7 +102,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.CLI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
@@ -125,7 +126,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.SEI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
@@ -141,7 +142,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
))
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
@@ -164,7 +165,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
))
irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 4
}
@@ -175,7 +176,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
))
irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
@@ -195,7 +196,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
))
irProg.chunks().single().instructions.size shouldBe 8
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 4
}
@@ -204,21 +205,27 @@ class TestIRPeepholeOpt: FunSpec({
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=42, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535)
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=42, immediate = -1)
))
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr.size shouldBe 6
instr[0].opcode shouldBe Opcode.LOAD
instr[1].opcode shouldBe Opcode.LOAD
instr[2].opcode shouldBe Opcode.LOAD
instr[3].opcode shouldBe Opcode.LOAD
instr[4].opcode shouldBe Opcode.LOAD
instr[5].opcode shouldBe Opcode.LOAD
instr[0].immediate shouldBe 0
instr[1].immediate shouldBe 0
instr[2].immediate shouldBe 255
instr[3].immediate shouldBe 65535
instr[2].immediate shouldBe 0
instr[3].immediate shouldBe 255
instr[4].immediate shouldBe 65535
instr[5].immediate shouldBe -1
}
})
+9 -8
View File
@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -43,7 +44,7 @@ class TestVmCodeGen: FunSpec({
// xx += cx16.r0
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -162,7 +163,7 @@ class TestVmCodeGen: FunSpec({
// nop
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -234,7 +235,7 @@ class TestVmCodeGen: FunSpec({
// nop
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -302,7 +303,7 @@ class TestVmCodeGen: FunSpec({
// goto $c000
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -358,7 +359,7 @@ class TestVmCodeGen: FunSpec({
// nop
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -430,7 +431,7 @@ class TestVmCodeGen: FunSpec({
// nop
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -498,7 +499,7 @@ class TestVmCodeGen: FunSpec({
// goto $c000
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
@@ -547,7 +548,7 @@ class TestVmCodeGen: FunSpec({
// routine()
// }
//}
val codegen = VmCodeGen()
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
@@ -373,7 +373,7 @@ class ConstExprEvaluator {
}
"isspace" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
}
"isprint" -> {
val char = args[0].number.toInt()
@@ -11,6 +11,7 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.BaseDataType
import prog8.code.core.IErrorReporter
import prog8.code.core.isInteger
import kotlin.math.floor
@@ -39,7 +40,10 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=BaseDataType.LONG) {
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
// do NOT reduce the type if the target of the assignment is a long
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
}
}
@@ -85,6 +89,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val modifications = mutableListOf<IAstModification>()
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -104,35 +110,55 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
}
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.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
else if (expr.operator=="*" && expr.left is StringLiteral) {
if (rightconst != null) {
// mutiply a string.
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
if(rightconst.number>255)
errors.err("string length must be 0-255", rightconst.position)
else if(!rightconst.type.isInteger)
errors.err("can only multiply string by integer constant", rightconst.position)
else {
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
}
}
// other errors are reported later
return noModifications
}
}
if(expr.left.inferType(program).isArray) {
if (expr.operator=="*" && rightconst!=null) {
if (expr.left is ArrayLiteral) {
// concatenate array literal.
val part = expr.left as ArrayLiteral
if(part.value.isEmpty())
errors.warn("resulting array has length zero", part.position)
val tmp = mutableListOf<Expression>()
repeat(rightconst.number.toInt()) {
part.value.forEach { tmp += it.copy() }
if (expr.operator=="*") {
if (rightconst != null) {
if (expr.left is ArrayLiteral) {
// concatenate array literal.
val part = expr.left as ArrayLiteral
if(part.value.isEmpty())
errors.warn("resulting array has length zero", part.position)
if(rightconst.number.toInt() !in 1..(255/part.value.size))
errors.err("array length must be 1-255", rightconst.position)
else if(!rightconst.type.isInteger)
errors.err("can only multiply array by integer constant", rightconst.position)
else {
val tmp = mutableListOf<Expression>()
repeat(rightconst.number.toInt()) {
part.value.forEach { tmp += it.copy() }
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
} else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
} else {
errors.err("can only multiply an array by an integer value", expr.right.position)
}
return noModifications
}
}
@@ -344,7 +370,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
}
} else if(arrayIndexedExpression.pointerderef!=null) {
TODO("constant fold pointer[i]")
TODO("constant fold pointer[i] ${arrayIndexedExpression.position}")
}
}
}
@@ -357,7 +383,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
else {
val const2 = evaluator.evaluate(functionCallExpr, program)
return if(const2!=null)
if(const2!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
else
noModifications
@@ -438,7 +464,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt issimpletype BaseDataType.LONG) {
if(valueDt issimpletype BaseDataType.LONG || decl.datatype.isLong) {
return noModifications // this is handled in the numericalvalue case
}
if(!(valueDt istype decl.datatype)) {
@@ -536,7 +562,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
} else {
return if(subleftIsConst) {
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
@@ -26,24 +26,20 @@ class VarConstantValueTypeAdjuster(
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
} catch (x: UndefinedSymbolError) {
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.isNumeric)
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || !decl.datatype.isNumericOrBool)
return noModifications
if (decl.value != null && decl.value!!.constValue(program) == null)
return noModifications
@@ -173,7 +169,12 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = if(t1.isBytes) {
if(t1 issimpletype BaseDataType.BYTE) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
if(t1 issimpletype BaseDataType.WORD) "clamp__word" else "clamp__uword"
when {
t1 issimpletype BaseDataType.WORD -> "clamp__word"
t1 issimpletype BaseDataType.UWORD -> "clamp__uword"
t1 issimpletype BaseDataType.LONG -> "clamp__long"
else -> throw FatalAstException("clamp type")
}
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@@ -195,12 +196,14 @@ class VarConstantValueTypeAdjuster(
else
"${funcName}__ubyte"
} else if(t1.isInteger && t2.isInteger) {
replaceFunc = if(t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD)
"${funcName}__word"
else
"${funcName}__uword"
replaceFunc = when {
t1 issimpletype BaseDataType.LONG || t2 issimpletype BaseDataType.LONG -> "${funcName}__long"
t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD -> "${funcName}__word"
t1 issimpletype BaseDataType.UWORD || t2 issimpletype BaseDataType.UWORD -> "${funcName}__uword"
else -> throw FatalAstException("min/max type")
}
} else if(t1.isNumeric && t2.isNumeric) {
errors.err("min/max not supported for floats", functionCallExpr.position)
errors.err("min/max not supported for floats, use floats.minf/maxf instead", functionCallExpr.position)
return noModifications
} else {
errors.err("expected numeric arguments", functionCallExpr.args[0].position)
@@ -218,6 +221,7 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = when {
dt.isSignedByte -> "abs__byte"
dt.isSignedWord -> "abs__word"
dt.isLong -> "abs__long"
dt.isFloat -> "abs__float"
dt.isUnsignedByte || dt.isUnsignedWord -> {
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
@@ -240,8 +244,16 @@ class VarConstantValueTypeAdjuster(
dt.isUnsignedByte -> "sqrt__ubyte"
dt.isUnsignedWord -> "sqrt__uword"
dt.isFloat -> "sqrt__float"
dt.isLong -> {
val value = functionCallExpr.args[0].constValue(program)?.number
if(value!=null && value<0) {
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
return noModifications
}
"sqrt__long"
}
else -> {
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
errors.err("expected numeric argument", functionCallExpr.args[0].position)
return noModifications
}
}
@@ -250,6 +262,15 @@ class VarConstantValueTypeAdjuster(
functionCallExpr))
}
}
else if(func==listOf("lsb") || func==listOf("msb")) {
val t1 = functionCallExpr.args[0].inferType(program)
if(t1.isLong) {
val replaceFunc = func[0]+"__long"
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
}
return noModifications
}
@@ -278,6 +299,25 @@ class VarConstantValueTypeAdjuster(
functionCallStatement))
}
}
else if(func==listOf("swap")) {
val t1 = functionCallStatement.args[0].inferType(program)
if(t1.isKnown) {
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
val replaceFunc = when {
dt.isByteOrBool -> "swap__byte"
dt.isWord || dt.isPointer -> "swap__word"
dt.isLong -> "swap__long"
dt.isFloat -> "swap__float"
else -> {
errors.err("expected numeric arguments", functionCallStatement.args[0].position)
return noModifications
}
}
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
functionCallStatement))
}
}
return noModifications
}
}
@@ -313,41 +353,36 @@ internal class ConstantIdentifierReplacer(
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
return noModifications
try {
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
when {
cval.type.isNumericOrBool -> {
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
)
}
when {
cval.type.isNumericOrBool -> {
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
)
}
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
)
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
}
}
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.log2
@@ -90,6 +91,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val newExpr = applyAbsorptionLaws(expr)
if(newExpr!=null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -244,15 +247,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (rightVal?.number == 1.0) {
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -266,15 +273,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (rightVal?.number == 1.0) {
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -318,6 +329,134 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
if(expr.operator=="<<") {
val amount = expr.right.constValue(program)?.number
val idt = expr.left.inferType(program)
if(amount==8.0 && idt.isWords) {
// msb(word<<8) --> lsb(word)
val msb = expr.parent as? IFunctionCall
if(msb?.target?.nameInSource == listOf("msb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
if(idt.isLong) {
when(amount) {
8.0 -> {
// msb(long<<8) --> lsb(msw(long))
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
}
}
16.0 -> {
// msw(long<<16) --> lsw(long)
val msw = expr.parent as? IFunctionCall
if(msw?.target?.nameInSource == listOf("msw"))
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
// msb(long<<16) --> msb(lsw(lv))
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
24.0 -> {
// msb(long<<24) --> lsb__long(long)
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(msb?.singleOrNull() in arrayOf("msb__long", "msb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
}
}
}
else if(expr.operator==">>") {
val amount = expr.right.constValue(program)?.number
val idt = expr.left.inferType(program)
if(amount==8.0 && idt.isWords) {
// lsb(word>>8) --> msb(word)
// word>>8 as ubyte --> msb(word)
val lsb = expr.parent as? IFunctionCall
if(lsb?.target?.nameInSource == listOf("lsb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true)
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
else if (idt.isLong) {
when(amount) {
8.0 -> {
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
// lsb(long>>8) --> msb(lsw(long))
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
}
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true) {
// long>>8 as ubyte --> msb(lsw(long))
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
16.0 -> {
// lsw(long>>16) --> msw(long)
// long>>16 as uword --> msw(long)
val lsw = expr.parent as? IFunctionCall
if(lsw?.target?.nameInSource == listOf("lsw"))
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isWord==true)
return listOf(IAstModification.ReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb") || castbyte?.type?.isByte==true) {
// lsb(long>>16) --> lsb(msw(long)
// long>>16 as ubyte --> msb(long)
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
}
}
24.0 -> {
val lsb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
// lsb(long>>24) --> msb(long)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
}
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true) {
// long>>24 as ubyte --> msb(long)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
}
}
}
// simplify when a term is constant and directly determines the outcome
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
val newExpr2 = when (expr.operator) {
@@ -424,19 +563,92 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
// fun isPowerOfTwo(number: Double): Boolean {
// val intValue = number.toInt()
// return intValue > 0 && (intValue and (intValue - 1)) == 0
// }
fun isFactorOf256(number: Double): Boolean {
val intValue = number.toInt()
return intValue >= 256 && (intValue and 0xFF) == 0
}
if (leftDt.isUnsignedWord && rightVal!=null) {
if (expr.operator == ">" && rightVal.number == 255.0 || expr.operator == ">=" && rightVal.number == 256.0) {
// uword > 255 --> msb(value)!=0
// uword >= 256 --> msb(value)!=0
expr.operator = "!="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">=" && isFactorOf256(rightVal.number)) {
// uword >= $xx00 --> msb(value)>=xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = ">="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">" && isFactorOf256(rightVal.number+1)) {
// uword > $xxFF --> msb(value)>xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = ">"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && rightVal.number == 256.0 || expr.operator == "<=" && rightVal.number == 255.0) {
// uword < 256 --> msb(value)==0
// uword <= 255 --> msb(value)==0
expr.operator = "=="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && isFactorOf256(rightVal.number)) {
// uword < $xx00 --> msb(value)<xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = "<"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator=="<=" && isFactorOf256(rightVal.number+1)) {
// uword <= $xxFF --> msb(value)<=xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = "<="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
if((arrayIndexedExpression.parent as? BinaryExpression)?.operator !=".") {
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
if(binexprParent?.operator!=".") {
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
if(dt.isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
}
} else if(arrayIndexedExpression.pointerderef==null) {
// possibly pointer[0].field --> pointer.field
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(target?.datatype?.isPointer==true) {
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
if(field!=null) {
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
}
}
}
}
val ptrDeref = arrayIndexedExpression.pointerderef
@@ -531,6 +743,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("msw")) {
// lsb(msw(longvar)) --> @(&longvar+2) ; get the bank byte from a long variable
val longvar = arg.args[0] as? IdentifierReference
if(longvar!=null && longvar.inferType(program).isLong) {
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 2.0, functionCallExpr.position), functionCallExpr.position)
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
}
} else {
if(arg is IdentifierReference && arg.nameInSource.size==2
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
@@ -563,6 +784,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
NumericLiteral(valueDt.getOr(DataType.UBYTE).base, 0.0, arg.expression.position),
parent))
}
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("lsw")) {
// msb(lsw(longvar)) --> @(&longvar+1) ; get the second byte from a long variable
val longvar = arg.args[0] as? IdentifierReference
if(longvar!=null && longvar.inferType(program).isLong) {
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 1.0, functionCallExpr.position), functionCallExpr.position)
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
}
} else {
if(arg is IdentifierReference && arg.nameInSource.size==2
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
@@ -22,6 +22,45 @@ class StatementOptimizer(private val program: Program,
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
when (functionName) {
"poke" -> {
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isBytes) {
// poke(&bytevar, x) --> bytevar=x
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
}
}
"pokew" -> {
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
if(addrOfIdentifier!=null && (addrOfIdentifier.inferType(program).isWords || addrOfIdentifier.inferType(program).isPointer)) {
// pokew(&wordvar, x) --> wordvar=x
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
}
}
"pokel" -> {
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isLong) {
// pokel(&longvar, x) --> longvar=x
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
}
}
"pokef" -> {
val addrOfIdentifier = (functionCallStatement.args[0] as? AddressOf)?.identifier
if(addrOfIdentifier!=null && addrOfIdentifier.inferType(program).isFloat) {
// pokef(&floatvar, x) --> floatvar=x
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = functionCallStatement.position)
val assign = Assignment(target, functionCallStatement.args[1], AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, assign, parent))
}
}
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
@@ -300,6 +339,17 @@ class StatementOptimizer(private val program: Program,
}
val memwriteAddr = assignment.target.memoryAddress
if(memwriteAddr!=null) {
val addrOfIdentifier = (memwriteAddr.addressExpression as? AddressOf)?.identifier
if(addrOfIdentifier!=null && (addrOfIdentifier.inferType(program).isBytes || addrOfIdentifier.inferType(program).isBool)) {
// @(&bytevar) = x --> bytevar = x
val target = AssignTarget(addrOfIdentifier, null, null, null, false, position = assignment.position)
val assign = Assignment(target, assignment.value, AssignmentOrigin.OPTIMIZER, assignment.position)
return listOf(IAstModification.ReplaceNode(assignment, assign, parent))
}
}
return noModifications
}
@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.compiler.CallGraph
@@ -43,6 +43,10 @@ class UnusedCodeRemover(private val program: Program,
val pop = subroutines.single { s -> s.name == "pop" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pop)
subroutines
.filter { s -> s.name == "internal_long_R1_to_float_AY" || s.name=="internal_long_AY_to_FAC" }
.forEach { sub -> neverRemoveSubroutines.add(sub) }
}
}
@@ -81,7 +85,7 @@ class UnusedCodeRemover(private val program: Program,
private fun reportUnreachable(stmt: Statement) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is Break, is Jump -> {}
else -> errors.warn("unreachable code", next.position)
}
}
@@ -93,7 +97,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
+2 -1
View File
@@ -2,7 +2,8 @@ plugins {
id("application")
kotlin("jvm")
// id("com.github.johnrengelman.shadow") version "8.1.1"
id("io.github.goooler.shadow") version "8.1.8"
// id("io.github.goooler.shadow") version "8.1.8"
id("com.gradleup.shadow") version "9.3.1"
id("com.peterabeles.gversion") version "1.10.3"
}
+144
View File
@@ -0,0 +1,144 @@
bcd {
; Decimal addition an subtraction routines.
; For CPUs that support BCD mode (binary coded decimal) (not all 6502 variants support this mode...)
; This is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string. Just print the hex representation.
sub addb(byte a, byte b) -> byte {
setbcd()
a += b
clearbcd()
return a
}
sub addub(ubyte a, ubyte b) -> ubyte {
setbcd()
a += b
clearbcd()
return a
}
sub addw(word a, word b) -> word {
setbcd()
a += b
clearbcd()
return a
}
sub adduw(uword a, uword b) -> uword {
setbcd()
a += b
clearbcd()
return a
}
sub addl(long a, long b) -> long {
setbcd()
a += b
clearbcd()
return a
}
sub addtol(^^long a, long b) {
; -- inplace long BCD addition (avoids copying long values by value)
setbcd()
;; NOT YET IMPLEMENTED IN PROG8: a^^ += b, so inline asm
%asm {{
lda p8v_a
ldy p8v_a+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
clc
lda (P8ZP_SCRATCH_W1),y
adc p8v_b
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
adc p8v_b+1
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
adc p8v_b+2
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
adc p8v_b+3
sta (P8ZP_SCRATCH_W1),y
}}
clearbcd()
}
sub subb(byte a, byte b) -> byte {
setbcd()
a -= b
clearbcd()
return a
}
sub subub(ubyte a, ubyte b) -> ubyte {
setbcd()
a -= b
clearbcd()
return a
}
sub subuw(uword a, uword b) -> uword {
setbcd()
a -= b
clearbcd()
return a
}
sub subl(long a, long b) -> long {
setbcd()
a -= b
clearbcd()
return a
}
sub subfroml(^^long a, long b) {
; -- inplace long BCD subtraction (avoids copying long values by value)
setbcd()
;; NOT YET IMPLEMENTED IN PROG8: a^^ -= b, so inline asm
%asm {{
lda p8v_a
ldy p8v_a+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
sec
lda (P8ZP_SCRATCH_W1),y
sbc p8v_b
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
sbc p8v_b+1
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
sbc p8v_b+2
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
sbc p8v_b+3
sta (P8ZP_SCRATCH_W1),y
}}
clearbcd()
}
inline asmsub setbcd() {
; be safe and 6502 compatible and prohibit interrupts during BCD mode
%asm {{
php
sei
sed
}}
}
inline asmsub clearbcd() {
%asm {{
plp
}}
}
}
+59 -1
View File
@@ -70,7 +70,65 @@ smallringbuffer {
}
}
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
smallstack {
; -- A small stack (LIFO) that uses just 256 bytes and is independent of the CPU stack. Stack is growing downward from the top of the buffer.
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
; note: for a "small stack" (256 bytes size) you might also perhaps just use the CPU stack via sys.push[w] / sys.pop[w].
ubyte[256] buffer
ubyte sp = 255
sub init() {
sp = 255
}
sub size() -> ubyte {
return 255-sp
}
sub free() -> ubyte {
return sp
}
sub isfull() -> bool {
return sp==0
}
sub isempty() -> bool {
return sp==255
}
sub push(ubyte value) {
; -- put a byte on the stack
buffer[sp] = value
sp--
}
sub pushw(uword value) {
; -- put a word on the stack (lsb first then msb)
buffer[sp] = lsb(value)
sp--
buffer[sp] = msb(value)
sp--
}
sub pop() -> ubyte {
; -- pops a byte off the stack
sp++
return buffer[sp]
}
sub popw() -> uword {
; -- pops a word off the stack.
sp++
cx16.r0H = buffer[sp]
sp++
cx16.r0L = buffer[sp]
return cx16.r0
}
}
stack {
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
+219 -280
View File
@@ -133,6 +133,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
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.
@@ -315,6 +338,8 @@ c128 {
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
&ubyte MMUCR = $ff00 ; MMU control register
&ubyte SCREEN_MODE = $d7 ; 40 or 80 column mode active?
extsub $FF47 = SPIN_SPOUT() clobbers(A) ; set up serial bus for fast communications mode
@@ -325,7 +350,7 @@ extsub $FF53 = BOOT_CALL(ubyte device @X, ubyte drive @A) clobbers(A,X,Y) ; try
extsub $FF56 = PHOENIX() clobbers(A,X,Y) ; search for and autostart ROMs, cartridges, then default disk
extsub $FF59 = LKUPLA(ubyte lfn @A) -> bool @Pc, ubyte @X ; look up logical file number to see if it's open; returns device
extsub $FF5C = LKUPSA(ubyte sa @Y) -> bool @Pc, ubyte @A, ubyte @X ; look up secondary address to see if it's in use; returns lfn and device
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column)
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column) (see set40/set80 if you want to do it idempotently)
extsub $FF62 = DLCHR() clobbers(A,X,Y) ; copy character ROM into VDC video RAM
extsub $FF65 = PFKEY(ubyte zpaddr @A, ubyte key @X, ubyte length @Y) ; redefine programmable function key (string descriptor in zp, addr in A)
extsub $FF68 = SETBNK(ubyte data_bank @A, ubyte filename_bank @X) ; set memory bank for load/save
@@ -335,7 +360,7 @@ extsub $FF71 = JMPFAR() clobbers(A,X) ; jump without r
extsub $FF74 = INDFET(ubyte zpaddr @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> ubyte @A ; fetch byte from another bank (address in zp, ptr in A)
extsub $FF77 = INDSTA(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) ; store byte to another bank (address in zp, ptr in $02b9)
extsub $FF7A = INDCMP(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> bool @Pz, bool @Pc, bool @Pv; compare byte in another bank (address in zp, ptr in $02c8)
extsub $FF7D = PRIMM() ; print immediate string
extsub $FF7D = PRIMM() ; print immediate string (the 0-terminated string immediately following the JSR PRIMM instruction)
; ---- C128 specific system utility routines: ----
@@ -343,14 +368,14 @@ extsub $FF7D = PRIMM() ; print immediat
inline asmsub banks(ubyte banks @A) {
; -- set the memory bank configuration MMU register
%asm {{
sta $FF00
sta MMUCR
}}
}
inline asmsub getbanks() -> ubyte @A {
; -- get the current memory bank configuration from the MMU register
%asm {{
lda $FF00
lda MMUCR
}}
}
@@ -370,11 +395,28 @@ asmsub disable_basic() clobbers(A) {
sta $033c
lda #$0e ; bank out BASIC ROM
sta $ff00
sta MMUCR
rts
}}
}
asmsub disable_fkey_macros() {
%asm {{
lda #183
sta 828
rts
}}
}
asmsub enable_fkey_macros() {
%asm {{
lda #173
sta 828
rts
}}
}
asmsub x16jsrfar() {
%asm {{
; setup a JSRFAR call (using X16 call convention)
@@ -425,10 +467,52 @@ asmsub x16jsrfar() {
}}
}
asmsub fast() {
; -- enable 2 MHz cpu mode, blanks VIC screen
%asm {{
lda #10
sta $d011 ; disable VIC-II display
lda $d030
ora #$01 ; Set FAST bit
sta $d030
rts
}}
}
asmsub slow() {
; -- disable 2 MHz cpu mode, makes VIC screen visible
%asm {{
lda $d030
and #$fe ; clear FAST bit
sta $d030
lda #27
sta $d011 ; enable VIC-II display
rts
}}
}
sub is80() -> bool {
; -- return true if 80 column mode is active, false if 40 column mode is active
return SCREEN_MODE & $80 == $80
}
sub set40() {
; -- set 40 column mode.
if is80() SWAPPER()
}
sub set80() {
; -- set 80 column mode.
if not is80() SWAPPER()
}
; ---- end of C128 specific system utility routines ----
}
%import shared_sys_functions
sys {
; ------- lowlevel system routines --------
@@ -461,51 +545,10 @@ sys {
p8_sys_startup.enable_runstop_and_charsetswitch()
}
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
.section BSS
save_SCRATCH_ZPB1 .byte ?
save_SCRATCH_ZPREG .byte ?
save_SCRATCH_ZPWORD1 .word ?
save_SCRATCH_ZPWORD2 .word ?
.send BSS
; !notreached!
}}
}
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 {{
php
sei
sta _vector
sty _vector+1
@@ -513,7 +556,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -545,6 +588,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -554,24 +598,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; enable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -593,40 +647,52 @@ _raster_irq_handler
pla
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
lda #0
sta $ff00 ; default bank 15
sta c128.MMUCR ; default bank 15
jmp (cbm.RESET_VEC)
}}
}
@@ -680,195 +746,25 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcpy
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne _larger
- cmp c64.RASTER
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
bit c64.SCROLY
bmi -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
; Compares two blocks of memory
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
%asm {{
sta P8ZP_SCRATCH_REG ; lsb(size)
sty P8ZP_SCRATCH_B1 ; msb(size)
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldx P8ZP_SCRATCH_B1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_REG ; lsb(size)
bne +
rts
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_REG ; lsb(size)
bne -
lda #0
rts
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
inline asmsub disable_caseswitch() {
%asm {{
@@ -921,20 +817,6 @@ _no_msb_size
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
inline asmsub progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
@@ -989,6 +871,48 @@ _no_msb_size
}}
}
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -1031,6 +955,16 @@ cx16 {
&word r14s = $1bfc
&word r15s = $1bfe
; signed long versions
&long r0r1sl = $1be0
&long r2r3sl = $1be4
&long r4r5sl = $1be8
&long r6r7sl = $1bec
&long r8r9sl = $1bf0
&long r10r11sl = $1bf4
&long r12r13sl = $1bf8
&long r14r15sl = $1bfc
; ubyte versions (low and high bytes)
&ubyte r0L = $1be0
&ubyte r1L = $1be2
@@ -1165,12 +1099,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
@@ -1186,12 +1114,12 @@ asmsub init_system() {
%asm {{
sei
lda #0
sta $ff00 ; select default bank 15
sta c128.MMUCR ; select default bank 15
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
lda #%00001110
sta $ff00 ; bank out basic rom so we have ram from $1c00-$bfff
sta c128.MMUCR ; bank out basic rom so we have ram from $1c00-$bfff
lda #6
sta c64.EXTCOL
lda #7
@@ -1199,6 +1127,12 @@ asmsub init_system() {
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
jsr c128.disable_fkey_macros
; basic is not banked in, adjust MEMTOP
ldx #<$c000
ldy #>$c000
clc
jsr cbm.MEMTOP
cli
rts
}}
@@ -1217,9 +1151,14 @@ asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
lda #0
sta $ff00 ; default bank 15
sta c128.MMUCR ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
ldx #<c128.MMUCR
ldy #>c128.MMUCR
clc
jsr cbm.MEMTOP ; adjust MEMTOP to original value again
jsr c128.enable_fkey_macros
lda _exitcarry
lsr a
lda _exitcode
+47 -50
View File
@@ -1,6 +1,7 @@
; 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.
; NOTE: most of the routines here do not work on the 80 column VDC text screen, but only on the 40 column VIC-II text screen.
%import syslib
%import conv
@@ -33,7 +34,6 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
@@ -75,7 +75,7 @@ asmsub get_cursor() -> ubyte @X, ubyte @Y {
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; ---- fill the VIC-II character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
%asm {{
@@ -90,7 +90,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; ---- clear the VIC-II character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
ldy #250
@@ -105,7 +105,7 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; ---- clear the VIC-II character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
ldy #250
@@ -134,7 +134,7 @@ sub uppercase() {
}
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
; ---- scroll the whole screen 1 character to the left
; ---- scroll the whole VIC-II text screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
@@ -173,7 +173,7 @@ _scroll_screen ; scroll only the screen memory
}
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A,X) {
; ---- scroll the whole screen 1 character to the right
; ---- scroll the whole VIC-II text screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
@@ -207,7 +207,7 @@ _scroll_screen ; scroll only the screen memory
}
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A,X) {
; ---- scroll the whole screen 1 character up
; ---- scroll the whole VIC-II text screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
@@ -241,7 +241,7 @@ _scroll_screen ; scroll only the screen memory
}
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A,X) {
; ---- scroll the whole screen 1 character down
; ---- scroll the whole VIC-II text screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
@@ -275,22 +275,19 @@ _scroll_screen ; scroll only the screen memory
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
; ---- sets the character in the VIC-II screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _screenrows,y
sta P8ZP_SCRATCH_W1
lda _screenrows+1,y
sta P8ZP_SCRATCH_W1+1
txa
clc
adc _screenrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ pla
ldy #0
tay
pla
sta (P8ZP_SCRATCH_W1),y
rts
@@ -300,43 +297,37 @@ _screenrows .word cbm.Screen + range(0, 1000, 40)
}
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
; ---- get the character in the VIC-II screen matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setchr._screenrows,y
sta P8ZP_SCRATCH_W1
lda setchr._screenrows+1,y
sta P8ZP_SCRATCH_W1+1
pla
clc
adc setchr._screenrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
tay
lda (P8ZP_SCRATCH_W1),y
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
; ---- set the color in A on the screen matrix at the given position
; ---- set the color in A on the VIC-II screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _colorrows,y
sta P8ZP_SCRATCH_W1
lda _colorrows+1,y
sta P8ZP_SCRATCH_W1+1
txa
clc
adc _colorrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ pla
ldy #0
tay
pla
sta (P8ZP_SCRATCH_W1),y
rts
@@ -346,48 +337,42 @@ _colorrows .word $d800 + range(0, 1000, 40)
}
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
; ---- get the color in the VIC-II screen color matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setclr._colorrows,y
sta P8ZP_SCRATCH_W1
lda setclr._colorrows+1,y
sta P8ZP_SCRATCH_W1+1
pla
clc
adc setclr._colorrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
tay
lda (P8ZP_SCRATCH_W1),y
and #15
rts
}}
}
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; ---- set char+color at the given position on the VIC-II screen
%asm {{
_charptr = P8ZP_SCRATCH_W1
_colorptr = P8ZP_SCRATCH_W2
lda row
asl a
tay
lda setchr._screenrows+1,y
sta _charptr+1
adc #$d4
sta _colorptr+1
lda setchr._screenrows,y
clc
adc col
sta _charptr
sta _colorptr
bcc +
inc _charptr+1
inc _colorptr+1
+ lda character
ldy #0
lda setchr._screenrows+1,y
sta _charptr+1
clc
adc #$d4
sta _colorptr+1
lda character
ldy col
sta (_charptr),y
lda charcolor
sta (_colorptr),y
@@ -406,6 +391,7 @@ asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr cbm.SCREEN
inx
txa
rts
}}
@@ -415,11 +401,22 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr cbm.SCREEN
iny
tya
rts
}}
}
asmsub size() clobbers(A) -> ubyte @X, ubyte @Y {
; -- returns the text screen width in X and height in Y (number of columns and rows)
%asm {{
jsr cbm.SCREEN
inx
iny
rts
}}
}
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
+55 -1
View File
@@ -2,7 +2,8 @@
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
; note: don't add too many constants here because they all end up in the resulting program
.section BSS
floats_temp_var .byte ?,?,?,?,? ; temporary storage for a float
@@ -126,6 +127,40 @@ cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
.pend
cast_as_long .proc
; convert float pointed to by R0 into a long pointed to by AY
; the reverse routine that converts a long to a float is elsewhere as internal_long_to_float()
FACHO = FAC_ADDR + 1
; save the target variable pointer on the stack
pha
tya
pha
lda cx16.r0L
ldy cx16.r0H
jsr MOVFM
jsr QINT
; restore the target variable pointer from the stack, and put the result in it
pla
sta P8ZP_SCRATCH_PTR+1
pla
sta P8ZP_SCRATCH_PTR
ldx #3
ldy #0
- lda FACHO,x
sta (P8ZP_SCRATCH_PTR),y
iny
dex
bpl -
rts
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@@ -485,3 +520,22 @@ popFAC .proc
rts
.pend
swap_floats .proc
; -- swap float values pointed to by P8ZP_SCRATCH_W1 and AY.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #4
- lda (P8ZP_SCRATCH_W1),y
sta floats_temp_var,y
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
dey
bpl -
ldy #4
- lda floats_temp_var,y
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
+2
View File
@@ -11,6 +11,7 @@ floats {
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
const uword FAC_ADDR = $61
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
@@ -167,6 +168,7 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
; could use signed GIVAYFAY and add 65536 if negative, but there's a custom routine in rom here
%asm {{
sty $62 ; facmo
sta $63
+4 -1
View File
@@ -2,8 +2,11 @@
func_sign_f_into_A .proc
; sign in A, also sets status flags
jsr MOVFM
jmp SIGN
jsr SIGN
cmp #0
rts
.pend
+3 -20
View File
@@ -288,29 +288,16 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled.
; Overdraws horizontal lines unfortunately.
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*2+1)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*2+1)
last_y4 = new_y4
}
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2>=0 {
radius--
@@ -319,10 +306,6 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*2+1)
horizontal_line(xcenter-yy, last_y4, yy*2+1)
}
+175 -271
View File
@@ -132,6 +132,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
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.
@@ -313,12 +336,13 @@ asmsub banks(ubyte banks @A) {
%asm {{
and #%00000111
sta P8ZP_SCRATCH_REG
php
sei
lda $01
and #%11111000
ora P8ZP_SCRATCH_REG
sta $01
cli
plp
rts
}}
}
@@ -434,6 +458,36 @@ _jmpfar_vec .word ?
}
}
c128 {
asmsub fast() {
; -- if in C64 mode on a C128: enable 2 MHz cpu mode. blanks VIC screen.
; On a real C64, only blacks the screen.
%asm {{
lda #10
sta $d011 ; disable VIC-II display
lda $d030
ora #$01 ; Set FAST bit
sta $d030
rts
}}
}
asmsub slow() {
; -- if in C64 mode on a C128: disable 2 MHz cpu mode, makes VIC screen visible
; On a real C64, only restores the screen.
%asm {{
lda $d030
and #$fe ; clear FAST bit
sta $d030
lda #27
sta $d011 ; enable VIC-II display
rts
}}
}
}
%import shared_sys_functions
sys {
; ------- lowlevel system routines --------
@@ -466,51 +520,10 @@ sys {
p8_sys_startup.enable_runstop_and_charsetswitch()
}
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
.section BSS
save_SCRATCH_ZPB1 .byte ?
save_SCRATCH_ZPREG .byte ?
save_SCRATCH_ZPWORD1 .word ?
save_SCRATCH_ZPWORD2 .word ?
.send BSS
; !notreached!
}}
}
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 {{
php
sei
sta _vector
sty _vector+1
@@ -518,7 +531,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -551,6 +564,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -560,24 +574,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -600,33 +624,46 @@ _raster_irq_handler
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
@@ -687,195 +724,25 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcpy
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
dey
beq +
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne _larger
- cmp c64.RASTER
bne -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
bit c64.SCROLY
bmi -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
; Compares two blocks of memory
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
%asm {{
sta P8ZP_SCRATCH_REG ; lsb(size)
sty P8ZP_SCRATCH_B1 ; msb(size)
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldx P8ZP_SCRATCH_B1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_REG ; lsb(size)
bne +
rts
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_REG ; lsb(size)
bne -
lda #0
rts
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
inline asmsub disable_caseswitch() {
%asm {{
@@ -928,20 +795,6 @@ _no_msb_size
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
inline asmsub progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
@@ -995,6 +848,49 @@ _no_msb_size
pla
}}
}
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -1038,6 +934,16 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; signed long versions
&long r0r1sl = $cfe0
&long r2r3sl = $cfe4
&long r4r5sl = $cfe8
&long r6r7sl = $cfec
&long r8r9sl = $cff0
&long r10r11sl = $cff4
&long r12r13sl = $cff8
&long r14r15sl = $cffc
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
@@ -1172,12 +1078,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
@@ -1207,6 +1107,8 @@ asmsub init_system() {
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
lda #$80
sta 650 ; enable key repeat for normal keys too
lda #PROG8_C64_BANK_CONFIG ; apply bank config
sta $01
and #1
@@ -1243,6 +1145,8 @@ asmsub cleanup_at_exit() {
jsr cbm.MEMTOP ; adjust MEMTOP down again
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
lda #0
sta 650 ; disable keyrepeats
lda _exitcarry
lsr a
lda _exitcode
+30 -38
View File
@@ -39,7 +39,6 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
@@ -285,16 +284,13 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
tya
asl a
tay
lda _screenrows,y
sta P8ZP_SCRATCH_W1
lda _screenrows+1,y
sta P8ZP_SCRATCH_W1+1
txa
clc
adc _screenrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ pla
ldy #0
tay
pla
sta (P8ZP_SCRATCH_W1),y
rts
@@ -310,15 +306,12 @@ asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
tya
asl a
tay
lda setchr._screenrows,y
sta P8ZP_SCRATCH_W1
lda setchr._screenrows+1,y
sta P8ZP_SCRATCH_W1+1
pla
clc
adc setchr._screenrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
tay
lda (P8ZP_SCRATCH_W1),y
rts
}}
@@ -331,16 +324,13 @@ asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
tya
asl a
tay
lda _colorrows,y
sta P8ZP_SCRATCH_W1
lda _colorrows+1,y
sta P8ZP_SCRATCH_W1+1
txa
clc
adc _colorrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ pla
ldy #0
tay
pla
sta (P8ZP_SCRATCH_W1),y
rts
@@ -356,16 +346,14 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
tya
asl a
tay
lda setclr._colorrows,y
sta P8ZP_SCRATCH_W1
lda setclr._colorrows+1,y
sta P8ZP_SCRATCH_W1+1
pla
clc
adc setclr._colorrows,y
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
tay
lda (P8ZP_SCRATCH_W1),y
and #15
rts
}}
}
@@ -378,20 +366,16 @@ _colorptr = P8ZP_SCRATCH_W2
lda row
asl a
tay
lda setchr._screenrows+1,y
sta _charptr+1
adc #$d4
sta _colorptr+1
lda setchr._screenrows,y
clc
adc col
sta _charptr
sta _colorptr
bcc +
inc _charptr+1
inc _colorptr+1
+ lda character
ldy #0
lda setchr._screenrows+1,y
sta _charptr+1
clc
adc #$d4
sta _colorptr+1
lda character
ldy col
sta (_charptr),y
lda charcolor
sta (_colorptr),y
@@ -424,6 +408,14 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub size() clobbers(A) -> ubyte @X, ubyte @Y {
; -- returns the text screen width in X and height in Y (number of columns and rows)
%asm {{
jmp cbm.SCREEN
}}
}
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
+209 -12
View File
@@ -6,8 +6,6 @@ conv {
; ----- number conversions to decimal strings ----
ubyte[16] @shared string_out ; result buffer for the string conversion routines (note: uses uninitialized ARRAY instead of STR, to force it to be allocated in BSS area so it's ROM-compatible)
asmsub str_ub0(ubyte value @A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
@@ -155,6 +153,30 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
}}
}
sub str_ulhex(long value) -> str {
%asm {{
lda value+3
jsr internal_ubyte2hex
sta string_out
sty string_out+1
lda value+2
jsr internal_ubyte2hex
sta string_out+2
sty string_out+3
lda value+1
jsr internal_ubyte2hex
sta string_out+4
sty string_out+5
lda value
jsr internal_ubyte2hex
sta string_out+6
sty string_out+7
lda #0
sta string_out+8
}}
return &string_out
}
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
@@ -228,15 +250,91 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
}}
}
sub str_l (long value) -> str {
; ---- convert the long value into decimal string form, without left padding 0s
; source: https://codebase64.net/doku.php?id=base:32_bit_hexadecimal_to_decimal_conversion
; NOTE: this is a pretty slow conversion, don't use for time critical displays!
bool negative
string_out[0] = '0'
if value<0 {
negative = true
value = -value
}
%asm {{
jsr hex2dec
ldx #0
- lda string_out+1,x
cmp #'0'
bne +
inx
bne -
+ cpx #10 ; just all 0? keep one
bne +
dex
+ ; x points at the rightmost leading 0
lda negative
beq +
lda #'-' ; sign in front
sta string_out,x
dex
bmi _done
+ ; x still points at rightmost leading 0
; remove leading 0's (there is at least one if we're here)
ldy #0
- lda string_out+1,x
sta string_out,y
iny
inx
cpx #10
bne -
lda #0
sta string_out,y
_done
lda #<string_out
ldy #>string_out
rts
hex2dec:
; converts 10 digits (32 bit values have max. 10 decimal digits)
ldx #9+1
l3 jsr div10
ora #$30
sta string_out,x
dex
bne l3
rts
; divides a 32 bit value by 10
; remainder is returned in akku
div10
ldy #32 ; 32 bits
lda #0
clc
l4 rol
cmp #10
bcc skip
sbc #10
skip rol value
rol value+1
rol value+2
rol value+3
dey
bpl l4
rts
}}
}
; ---- string conversion to numbers -----
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
asmsub any2uword(str string @AY) -> uword @AY, ubyte @X {
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
; (the latter two require a $ or % prefix to be recognised)
; (any non-digit character will terminate the number string that is parsed)
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
; if the string was invalid, 0 will be returned in A.
; returns the parsed number word in AY, and the number of processed characters (including the prefix symbol) in X.
; if the string was invalid, 0 will be returned as count in X (and the word value in AY will be undefined).
%asm {{
pha
sta P8ZP_SCRATCH_W1
@@ -257,13 +355,7 @@ _hex pla
_bin pla
jsr bin2uword
_result
pha
lda cx16.r15
sta P8ZP_SCRATCH_B1 ; result value
pla
sta cx16.r15
sty cx16.r15+1
lda P8ZP_SCRATCH_B1
ldx cx16.r15
rts
}}
}
@@ -480,6 +572,109 @@ _try_iso
}}
}
asmsub hex2long(str string @AY) -> long @R0R1 {
; -- hexadecimal string (with or without '$') to long.
; string may be in petscii or c64-screencode encoding.
; stops parsing at the first character that's not a hex digit (except leading $)
; result in R0:R1, number of characters processed also remains in cx16.r15L if you want to use it!! (0 = error)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty cx16.r0
sty cx16.r0+1
sty cx16.r0+2
sty cx16.r0+3
sty cx16.r15H
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
bne _loop
iny
_loop
lda #0
sta P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #7 ; screencode letters A-F are 1-6
bcc _add_letter
and #127
cmp #97
bcs _try_iso ; maybe letter is iso:'a'-iso:'f' (97-102)
cmp #'g'
bcs _stop
cmp #'a'
bcs _add_letter
cmp #'0'
bcc _stop
cmp #'9'+1
bcs _stop
_calc
asl cx16.r0
rol cx16.r0+1
rol cx16.r0+2
rol cx16.r0+3
asl cx16.r0
rol cx16.r0+1
rol cx16.r0+2
rol cx16.r0+3
asl cx16.r0
rol cx16.r0+1
rol cx16.r0+2
rol cx16.r0+3
asl cx16.r0
rol cx16.r0+1
rol cx16.r0+2
rol cx16.r0+3
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora cx16.r0
sta cx16.r0
iny
bne _loop
_stop
sty cx16.r15L
rts
_add_letter
pha
lda #9
sta P8ZP_SCRATCH_B1
pla
jmp _calc
_try_iso
cmp #103
bcs _stop
and #63
bne _add_letter
; !notreached!
}}
}
sub str2long(str string) -> long {
; -- convert a decimal string (terminated with a zero byte) into a long. Clobbers R0
long result = 0
bool @nozp negative = string[0] == '-'
if negative or string[0]=='+'
string++
alias digit_char = cx16.r0L
repeat {
digit_char = @(string)
if digit_char in '0' to '9' {
result = (result<<1) + (result<<3) ; multiply by 10
result += digit_char - '0' ; add digit
} else {
break ; Invalid character, stop processing
}
string++
}
if negative
return -result
else
return result
}
asmsub bin2uword(str string @AY) -> uword @AY {
; -- binary string (with or without '%') to uword.
; stops parsing at the first character that's not a 0 or 1. (except leading %)
@@ -767,4 +962,6 @@ output .fill 5 ; 0-terminated output buffer (to make printing easier)
}}
}
ubyte[16] @shared string_out ; internal result buffer for the string conversion routines (note: uses uninitialized ARRAY instead of STR, to force it to be allocated in BSS area so it's ROM-compatible)
}
+61 -3
View File
@@ -68,7 +68,65 @@ smallringbuffer {
}
}
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
smallstack {
; -- A small stack (LIFO) that uses just 256 bytes and is independent of the CPU stack. Stack is growing downward from the top of the buffer.
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
; note: for a "small stack" (256 bytes size) you might also perhaps just use the CPU stack via sys.push[w] / sys.pop[w].
ubyte[256] buffer
ubyte sp = 255
sub init() {
sp = 255
}
sub size() -> ubyte {
return 255-sp
}
sub free() -> ubyte {
return sp
}
sub isfull() -> bool {
return sp==0
}
sub isempty() -> bool {
return sp==255
}
sub push(ubyte value) {
; -- put a byte on the stack
buffer[sp] = value
sp--
}
sub pushw(uword value) {
; -- put a word on the stack (lsb first then msb)
buffer[sp] = lsb(value)
sp--
buffer[sp] = msb(value)
sp--
}
sub pop() -> ubyte {
; -- pops a byte off the stack
sp++
return buffer[sp]
}
sub popw() -> uword {
; -- pops a word off the stack.
sp++
cx16.r0H = buffer[sp]
sp++
cx16.r0L = buffer[sp]
return cx16.r0
}
}
stack {
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
@@ -78,7 +136,7 @@ stack {
ubyte bank
sub init(ubyte rambank) {
; -- initialize the stack, must be called before use. Supply the HiRAM bank to use as buffer space.
; -- initialize the stack, must be called before use. Supply the HIRAM bank to use as buffer space.
sp = $bfff
bank = rambank
}
@@ -157,7 +215,7 @@ ringbuffer {
ubyte bank = 255 ; set via init()
sub init(ubyte rambank) {
; -- initialize the ringbuffer, must be called before use. Supply the HiRAM bank to use as buffer space.
; -- initialize the ringbuffer, must be called before use. Supply the HIRAM bank to use as buffer space.
head = $a000
tail = $bfff
fill = 0
+1 -1
View File
@@ -1,4 +1,4 @@
; **experimental** data compression/decompression routines, API subject to change!!
; data compression/decompression routines
%import shared_compression
+202 -107
View File
@@ -176,7 +176,7 @@ io_error:
sub list_filenames(str 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 separated 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 (case-sensitive).
; 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).
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
@@ -187,6 +187,7 @@ io_error:
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" {
@@ -200,8 +201,45 @@ io_error:
}
}
}
lf_end_list()
}
lf_end_list()
@(filenames_buffer)=0
sys.clear_carry()
return files_found
}
sub list_filenames_nocase(str lowercase_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 separated by a 0 byte. You can provide an optional pattern to match against (case-insensitive, ISO-encoding).
; Pattern needs to be in lowercase already.
; 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).
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
; Note that no list of pointers of some form is returned, the names are just squashed together.
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
; and registering when the next one starts after the 0-byte separator.
uword buffer_start = filenames_buffer
ubyte files_found = 0
filenames_buffer[0]=0
if lf_start_list(lowercase_pattern_ptr) {
while lf_next_entry_nocase() {
if list_filetype!="dir" {
filenames_buffer += strings.copy(list_filename, filenames_buffer) + 1
files_found++
if filenames_buffer - buffer_start > filenames_buf_size-20 {
@(filenames_buffer)=0
lf_end_list()
sys.set_carry()
return files_found
}
}
}
}
lf_end_list()
@(filenames_buffer)=0
sys.clear_carry()
return files_found
@@ -244,6 +282,7 @@ io_error:
; this version it only returns directory entries!
; note: only a single iteration loop can be active at a time!
cbm.SETNAM(5, "$:*=d")
diskio.lf_start_list.pattern_ptr = pattern_ptr
goto diskio.lf_start_list.start_list_internal
}
@@ -252,6 +291,20 @@ io_error:
; this version only returns actual file entries!
; note: only a single iteration loop can be active at a time!
cbm.SETNAM(5, "$:*=p")
diskio.lf_start_list.pattern_ptr = pattern_ptr
goto diskio.lf_start_list.start_list_internal
}
sub lf_start_list_having_prefix(str prefix) -> bool {
; -- start an iterative directory contents listing for entries with given prefix.
; note: only a single iteration loop can be active at a time!
list_filename[0] = '$'
list_filename[1] = ':'
cx16.r0L = strings.copy(prefix, &list_filename+2)
list_filename[cx16.r0L+2] = '*'
list_filename[cx16.r0L+3] = 0
cbm.SETNAM(cx16.r0L+3, list_filename)
diskio.lf_start_list.pattern_ptr = 0
goto diskio.lf_start_list.start_list_internal
}
@@ -259,69 +312,100 @@ io_error:
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred).
; If a match pattern was specified, it will be matched CASE SENSITIVELY against the filename.
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
if not iteration_in_progress
return false
repeat {
reset_read_channel() ; use the input io channel again
^^ubyte nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
if cbm.READST()!=0
goto close_end
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while cbm.CHRIN()!='\"' {
if cbm.READST()!=0
goto close_end
}
; read the filename
repeat {
ubyte character = cbm.CHRIN()
if character==0
break
if character=='\"'
break
@(nameptr) = character
nameptr++
}
@(nameptr) = 0
do {
cx16.r15L = cbm.CHRIN()
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
list_filetype[0] = cx16.r15L
list_filetype[1] = cbm.CHRIN()
list_filetype[2] = cbm.CHRIN()
while cbm.CHRIN()!=0 {
; read the rest of the entry until the end
}
void cbm.CHRIN() ; skip 2 bytes
void cbm.CHRIN()
if not internal_next_entry()
break
if not list_skip_disk_name {
if list_pattern==0
return true
if strings.pattern_match(list_filename, list_pattern)
if list_pattern==0 or strings.pattern_match(list_filename, list_pattern)
return true
}
list_skip_disk_name = false
}
close_end:
lf_end_list()
return false
}
sub lf_next_entry_nocase() -> bool {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred).
; If a match pattern was specified, it will be matched CASE INSENSITIVELY against the filename, in ISO-encoding.
; (match pattern has to be in lowercase as well)
; Note: NO case-folding is done in this routine! (unlike DOS"$ which does case folding on the basic prompt)
if not iteration_in_progress
return false
repeat {
if not internal_next_entry()
break
if not list_skip_disk_name {
if list_pattern==0 or strings.pattern_match_nocase(list_filename, list_pattern, true)
return true
}
list_skip_disk_name = false
}
lf_end_list()
return false
}
sub internal_next_entry() -> bool {
reset_read_channel() ; use the input io channel again
^^ubyte nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
if cbm.READST()!=0
return false
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while cbm.CHRIN()!='\"' {
if cbm.READST()!=0
return false
}
; read the filename
repeat {
ubyte character = cbm.CHRIN()
if character==0
break
if character=='\"'
break
@(nameptr) = character
nameptr++
}
@(nameptr) = 0
do {
cx16.r15L = cbm.CHRIN()
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
list_filetype[0] = cx16.r15L
list_filetype[1] = cbm.CHRIN()
list_filetype[2] = cbm.CHRIN()
while cbm.CHRIN()!=0 {
; read the rest of the entry until the end
}
void cbm.CHRIN() ; skip 2 bytes
void cbm.CHRIN()
return true
}
sub lf_end_list() {
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
@@ -339,6 +423,7 @@ close_end:
; 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: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
; 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()
@@ -566,7 +651,7 @@ return_status:
sub internal_f_open_w(str filename, bool open_for_seeks) -> bool {
f_close_w()
list_filename = filename
void strings.copy(filename, list_filename)
str modifier = ",s,?"
modifier[3] = 'w'
if open_for_seeks
@@ -624,7 +709,7 @@ done:
return list_filename
io_error:
list_filename = "io error"
void strings.copy("io error", list_filename)
goto done
}
@@ -809,6 +894,7 @@ io_error:
if cbm.READST()!=0
cx16.r0 = 0
}
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
return cx16.r0
}
@@ -818,8 +904,13 @@ io_error:
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
; Calculates the number of bytes loaded (files > 64Kb are truncated to 16 bits)
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> long {
&long banksize = &cx16.r12
banksize = cx16.getrambank() - startbank
banksize <<= 13 ; * 8192
banksize += endaddress
banksize -= startaddress
return banksize
}
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> bool @A {
@@ -986,6 +1077,7 @@ io_error:
sub exists(str filename) -> bool {
; -- returns true if the given file exists on the disk, otherwise false
; DON'T use this if you already have a file open with f_open!
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
; NOTE: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
if f_open(filename) {
f_close()
@@ -994,15 +1086,12 @@ io_error:
return false
}
sub f_seek(uword pos_hiword, uword pos_loword) {
sub f_seek(long position) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
ubyte[6] command = ['p',0,0,0,0,0]
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)
command[5] = msb(pos_hiword)
pokel(&command+2, position)
cbm.SETNAM(sizeof(command), &command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
@@ -1011,13 +1100,10 @@ io_error:
}
sub f_seek_w(uword pos_hiword, uword pos_loword) {
sub f_seek_w(long position) {
; -- 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)
pokel(&diskio.f_seek.command+2, position)
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
@@ -1025,54 +1111,63 @@ io_error:
reset_write_channel() ; back to the write io channel
}
asmsub f_tell() -> uword @R0, uword @R1, uword @R2, uword @R3 {
; -- Returns the current read position of the opened read file,
; in R0 and R1 (low + high words) and the file size in R2 and R3 (low + high words).
; Returns 0 as size if the command is not supported by the DOS implementation/version.
%asm {{
jmp internal_f_tell
}}
}
sub internal_f_tell() {
; gets the (32 bits) position + file size of the opened read file channel
ubyte[2] command = ['t',0]
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
cbm.SETNAM(sizeof(command), &command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
void cbm.CHKIN(15) ; use #15 as input channel
sub f_tell() -> long, long {
; -- Returns the current read position of the opened read file, and the file size.
; Returns 0,0 if the command is not supported by the DOS implementation/version.
ubyte[3] command = ['?','?',0]
bool success=false
; valid response starts with "07," followed by hex notations of the position and filesize
if cbm.CHRIN()=='0' and cbm.CHRIN()=='7' and cbm.CHRIN()==',' {
cx16.r1 = read4hex()
cx16.r0 = read4hex() ; position in R1:R0
void cbm.CHRIN() ; separator space
cx16.r3 = read4hex()
cx16.r2 = read4hex() ; filesize in R3:R2
success = true
long filesize, filepos
ubyte commandoffset
command[1] = 't'
commandoffset = 1
f_tell_internal()
return filepos, filesize
sub f_tell_internal() {
; this code is used for both the T (tell) and FL (fatlba) commands
command[2] = READ_IO_CHANNEL ; f_open uses this secondary address
cbm.SETNAM(sizeof(command)-commandoffset, &command+commandoffset)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
void cbm.CHKIN(15) ; use #15 as input channel
; valid response starts with "07," followed by hex notations of the position and filesize
if cbm.CHRIN()=='0' and cbm.CHRIN()=='7' and cbm.CHRIN()==',' {
filepos = read8hex()
void cbm.CHRIN() ; separator space
filesize = read8hex()
success = true
} else {
filesize = filepos = 0
}
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
}
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
}
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
}
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
if success
return
cx16.r0 = cx16.r1 = cx16.r2 = cx16.r3 = 0
}
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
sub f_fatlba() -> long, long {
; -- Return the current LBA, cluster number (sector index and shift value are ignored for now)
diskio.f_tell.command[0] = 'f'
diskio.f_tell.command[1] = 'l'
diskio.f_tell.commandoffset = 0
diskio.f_tell.f_tell_internal()
return diskio.f_tell.filepos, diskio.f_tell.filesize
}
sub read8hex() -> long {
str hex = "00000000"
for cx16.r0L in 0 to 7 {
hex[cx16.r0L] = cbm.CHRIN()
}
return conv.hex2long(hex)
}
}
+2 -3
View File
@@ -74,9 +74,8 @@ emudbg {
EMU_CPUCLK_L = 0
}
asmsub cpu_cycles() -> uword @R0, uword @R1 {
; -- returns the 32 bits cpu clock counter in R1:R0,
; returns lower 16 bits in R0, upper 16 bits in R1.
asmsub cpu_cycles() -> long @R0R1 {
; -- returns the 32 bits cpu clock counter in R1:R0
%asm {{
lda p8v_EMU_CPUCLK_L
sta cx16.r0L
+14 -7
View File
@@ -14,6 +14,8 @@ floats {
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
const uword FAC_ADDR = $c3
extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
@@ -137,14 +139,19 @@ asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
; See "basic.sym" kernal symbol file for the facmo memory location.
; TODO find a way to not depend on that internal fac memory location. MOVFM doesn't work.
%asm {{
sty $c4 ; facmo
sta $c5 ; facmo+1
ldx #$90
sec
jmp FLOATC
; treat as signed first
jsr GIVAYFAY
jsr SIGN
bpl +
; add 65536 to make it positive again
lda #<_float_const_65536
ldy #>_float_const_65536
jmp FADD
+ rts
_float_const_65536 .byte $91, $00, $00, $00, $00 ; float 65536.0
; !notreached!
}}
}
+4 -37
View File
@@ -433,25 +433,12 @@ gfx_hires {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2>=0 {
radius--
@@ -460,11 +447,6 @@ gfx_hires {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -474,23 +456,12 @@ gfx_hires {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2>=0 {
radius--
@@ -499,10 +470,6 @@ gfx_hires {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
+12 -44
View File
@@ -505,25 +505,11 @@ gfx_lores {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2>=0 {
radius--
@@ -532,11 +518,6 @@ gfx_lores {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -546,29 +527,22 @@ gfx_lores {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
uword liney = ycenter+yy
if msb(liney)==0
safe_horizontal_line(xcenter-radius, lsb(ycenter+yy), radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, lsb(liney), radius*$0002+1, color)
liney = ycenter-yy
if msb(liney)==0
safe_horizontal_line(xcenter-radius, lsb(ycenter-yy), radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
if msb(last_y3)==0
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
if msb(last_y4)==0
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
last_y4 = new_y4
}
safe_horizontal_line(xcenter-radius, lsb(liney), radius*$0002+1, color)
liney = ycenter+radius
if msb(liney)==0
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
liney = ycenter-radius
if msb(liney)==0
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
yy++
if decisionOver2>=0 {
radius--
@@ -577,12 +551,6 @@ gfx_lores {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
if msb(last_y3)==0
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
if msb(last_y4)==0
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
}
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
+5 -35
View File
@@ -595,23 +595,12 @@ drawmode: ora cx16.r15L
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2>=0 {
radius--
@@ -620,10 +609,6 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
@@ -634,23 +619,12 @@ drawmode: ora cx16.r15L
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
last_y4 = new_y4
}
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2>=0 {
radius--
@@ -659,10 +633,6 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, draw)
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
@@ -1038,7 +1008,7 @@ _doplot beq +
; y*40 lookup table. Pretty compact because it all fits in a word and we only need 240 y positions.
; a y*80 lookup table would be very large (lo,mid,hi for 480 values...)
uword[240] @split @shared times40 = [
uword[240] @shared times40 = [
0, 40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600,
640, 680, 720, 760, 800, 840, 880, 920, 960, 1000, 1040, 1080, 1120, 1160,
1200, 1240, 1280, 1320, 1360, 1400, 1440, 1480, 1520, 1560, 1600, 1640, 1680,
+1 -1
View File
@@ -283,7 +283,7 @@ palette {
; NOTE: this routine requires rom version 49+
alias set_default = cx16.set_default_palette
; get the bank and address of the word-array containing the 256 default palette colors
; get the bank and address (in A, and XY) of the word-array containing the 256 default palette colors
; NOTE: this routine requires rom version 49+
alias get_default = cx16.get_default_palette
}
Loaded 100 of 314 files, more files were not shown because too many files have changed in this diff. Show more