Compare commits

...
195 Commits
Author SHA1 Message Date
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
230 changed files with 9721 additions and 4078 deletions

No files matched your search

-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.21" />
<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.21/kotlin-stdlib-jdk8-2.2.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21.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.21/kotlin-stdlib-jdk7-2.2.21.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.21/kotlin-stdlib-jdk8-2.2.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21-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.21/kotlin-stdlib-jdk7-2.2.21-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.21/kotlin-stdlib-jdk8-2.2.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.21-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.21/kotlin-stdlib-jdk7-2.2.21-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 -1
View File
@@ -175,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)
+14 -1
View File
@@ -2,15 +2,28 @@
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 the smallest by a fair bit.
* 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)
+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
}
}
+5 -5
View File
@@ -1,5 +1,5 @@
%import textio
%import floats
%import ciatimer
; note: Prog8 has a complete CRC16 routine in its library: math.crc16
@@ -7,12 +7,11 @@
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -40,12 +39,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uwhex(EXPECTED, true)
txt.nl()
return true
}
+5 -5
View File
@@ -1,5 +1,5 @@
%import textio
%import floats
%import ciatimer
; note: Prog8 has a complete CRC32 routine in its library: math.crc32
@@ -7,12 +7,11 @@
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -40,12 +39,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ulhex(EXPECTED, true)
txt.nl()
return true
}
+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -36,12 +35,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ubhex(EXPECTED, true)
txt.nl()
return true
}
+3 -3
View File
@@ -1,16 +1,16 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 65 KiB

+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -46,12 +45,13 @@ test {
if prime_count == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -46,12 +45,13 @@ test {
if prime_count == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
+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)
+9 -3
View File
@@ -26,6 +26,7 @@ main {
str[20] benchmark_names
uword[20] benchmark_score
str version = "12.1"
sub start() {
@@ -35,8 +36,10 @@ main {
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version v12\n\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
@@ -88,7 +91,10 @@ main {
txt.color2(1, 6)
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark (v12)\n\n")
txt.print("\nscore benchmark (")
txt.print(version)
txt.print(")\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
+2 -2
View File
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
kotlin("jvm") version "2.2.21"
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)
@@ -37,7 +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_32)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
@@ -46,7 +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_32)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
@@ -63,7 +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_32, 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)
@@ -89,8 +89,8 @@ 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)),
"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)),
@@ -100,6 +100,8 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"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)),
@@ -114,6 +116,11 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"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)),
@@ -167,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")
@@ -18,6 +18,7 @@ class CompilationOptions(val output: OutputType,
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,
+4 -1
View File
@@ -14,8 +14,11 @@ fun Number.toHex(): String {
// larger -> "$12345678"
// negative values are prefixed with '-'.
val integer = this.toLong()
if(integer<0)
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')
+24 -19
View File
@@ -51,6 +51,7 @@ val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType
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.isLong get() = this == BaseDataType.LONG
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
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.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
@@ -402,7 +403,7 @@ enum class RegisterOrPair {
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1_32, R2R3_32, R4R5_32, R6R7_32, R8R9_32, R10R11_32, R12R13_32, R14R15_32;
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
companion object {
val names by lazy { entries.map { it.toString()} }
@@ -415,15 +416,19 @@ 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_32 -> "r0"
R2R3_32 -> "r2"
R4R5_32 -> "r4"
R6R7_32 -> "r6"
R8R9_32 -> "r8"
R10R11_32 -> "r10"
R12R13_32 -> "r12"
R14R15_32 -> "r14"
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")
}
@@ -447,7 +452,7 @@ enum class RegisterOrPair {
}
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
fun isLong() = this in combinedLongRegisters
fun isLong() = this in CombinedLongRegisters
} // only used in parameter and return value specs in asm subroutines
@@ -484,15 +489,15 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val combinedLongRegisters = arrayOf(
RegisterOrPair.R0R1_32,
RegisterOrPair.R2R3_32,
RegisterOrPair.R4R5_32,
RegisterOrPair.R6R7_32,
RegisterOrPair.R8R9_32,
RegisterOrPair.R10R11_32,
RegisterOrPair.R12R13_32,
RegisterOrPair.R14R15_32
val CombinedLongRegisters = arrayOf(
RegisterOrPair.R0R1,
RegisterOrPair.R2R3,
RegisterOrPair.R4R5,
RegisterOrPair.R6R7,
RegisterOrPair.R8R9,
RegisterOrPair.R10R11,
RegisterOrPair.R12R13,
RegisterOrPair.R14R15
)
val CpuRegisters = arrayOf(
@@ -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))
}
}
@@ -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)
@@ -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
@@ -347,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)
@@ -529,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)
@@ -550,10 +548,10 @@ 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)) {
@@ -564,11 +562,11 @@ class AsmGen6502Internal (
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
}
}
}
@@ -815,7 +813,7 @@ class AsmGen6502Internal (
RegisterOrPair.AY,
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
in combinedLongRegisters -> 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")
@@ -948,7 +946,7 @@ class AsmGen6502Internal (
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)
require(target.register in CombinedLongRegisters)
val startreg = target.register!!.startregname()
out("""
lda $valuesym
@@ -962,14 +960,14 @@ class AsmGen6502Internal (
}
} else throw AssemblyError("weird casted type")
} else {
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - use simple expressions and temporary variables for now")
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} - use simple expressions and temporary variables for now")
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 -> {
@@ -1575,7 +1573,8 @@ $repeatLabel""")
if(addrOf.isFromArrayElement) {
TODO("address-of array element ${addrOf.position}")
} else if(addrOf.dereference!=null) {
TODO("read &dereference ${addrOf.position}")
if(pointerGen.readByteByAddressOfDereference(addrOf, constOffset))
return true
} else {
out(" lda ${asmSymbolName(addrOf.identifier!!)}+${constOffset}")
return true
@@ -2003,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)
@@ -2124,20 +2123,21 @@ $repeatLabel""")
}
}
internal fun loadIndirectLongIntoR14R15(zpPtrVar: String, offset: UByte) {
internal fun loadIndirectLongIntoCombinedLongRegister(zpPtrVar: String, offset: UByte, targetRegPair: RegisterOrPair) {
val regstart = targetRegPair.startregname()
out("""
ldy #$offset
lda ($zpPtrVar),y
sta cx16.r14
sta cx16.$regstart
iny
lda ($zpPtrVar),y
sta cx16.r14+1
sta cx16.$regstart+1
iny
lda ($zpPtrVar),y
sta cx16.r14+2
sta cx16.$regstart+2
iny
lda ($zpPtrVar),y
sta cx16.r14+3""")
sta cx16.$regstart+3""")
}
internal fun storeIndirectByte(byte: Int, zpPtrVar: String, offset: UByte) {
@@ -2442,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) {
@@ -2454,12 +2482,13 @@ $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("""
sta P8ZP_SCRATCH_REG
tax
lda cx16.$startregName
pha
lda cx16.$startregName+1
@@ -2468,12 +2497,12 @@ $repeatLabel""")
pha
lda cx16.$startregName+3
pha
lda P8ZP_SCRATCH_REG""")
txa""")
return
}
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
out("""
sta P8ZP_SCRATCH_REG
tax
sty P8ZP_SCRATCH_B1
ldy #0
- lda cx16.$startregName,y
@@ -2482,7 +2511,7 @@ $repeatLabel""")
cpy #$numbytes
bne -
ldy P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_REG""")
txa""")
}
internal fun popLongRegisters(startreg: RegisterOrPair, numberOfLongs: Int) {
@@ -2491,7 +2520,7 @@ $repeatLabel""")
val startregName = startreg.startregname()
if(numberOfLongs==1) {
out("""
sta P8ZP_SCRATCH_REG
tax
pla
sta cx16.$startregName+3
pla
@@ -2500,12 +2529,12 @@ $repeatLabel""")
sta cx16.$startregName+1
pla
sta cx16.$startregName
lda P8ZP_SCRATCH_REG""")
txa""")
return
}
val numbytes = numberOfLongs * options.compTarget.memorySize(BaseDataType.LONG)
out("""
sta P8ZP_SCRATCH_REG
tax
sty P8ZP_SCRATCH_B1
ldy #$numbytes-1
- pla
@@ -2513,7 +2542,7 @@ $repeatLabel""")
dey
bpl -
ldy P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_REG""")
txa""")
}
}
@@ -2531,5 +2560,6 @@ internal class SubroutineExtraAsmInfo {
}
internal fun Double.toLongHex(): String = this.toUInt().toString(16).padStart(8, '0')
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()
@@ -544,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)) {
@@ -811,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
@@ -727,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")
@@ -835,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()
}
@@ -904,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)
@@ -931,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)
@@ -967,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,
@@ -205,13 +205,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword" || arg.name == "mklong2")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
if (arg.name == "mklong")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
return !arg.isSimple()
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
@@ -279,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)
@@ -290,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
@@ -322,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 {
@@ -362,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
@@ -942,7 +942,7 @@ _jump jmp (${target.asmLabel})
asmgen.out(" lda $varname+1")
}
is PtAddressOf -> {
require(!long)
require(!long) {"addresses must still be words not longs"}
if(value.isFromArrayElement) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
@@ -955,9 +955,14 @@ _jump jmp (${target.asmLabel})
}
}
else -> {
require(!long)
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")
}
}
}
}
@@ -1268,15 +1273,15 @@ _jump jmp (${target.asmLabel})
ora $varname+2
ora $varname+3""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
asmgen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, value.type.isSigned)
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_32, 1)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG ; restore flags")
}
if(notEquals) {
@@ -1294,7 +1299,11 @@ _jump jmp (${target.asmLabel})
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long <= 0 ${value.position}")
checkLongLeZeroIntoCarry(value)
if (jump != null)
translateJumpElseBodies("bcs", "bcc", jump, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
@@ -1304,15 +1313,59 @@ _jump jmp (${target.asmLabel})
}
}
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long >= 0 ${value.position}")
} else {
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")
}
}
@@ -1435,7 +1488,7 @@ _jump jmp (${target.asmLabel})
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_32, left.type.isSigned)
asmgen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
if(constRight!=null) {
val hex = constRight.toLongHex()
asmgen.out("""
@@ -1493,17 +1546,13 @@ _jump jmp (${target.asmLabel})
) {
// 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(left !is PtNumber && left !is PtIdentifier || right !is PtNumber && right !is PtIdentifier) {
TODO("long comparison $operator with expressions - use temporary long variable instead to simplify for now ${left.position}")
}
if(operator=="<" || operator ==">=") {
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R14R15_32, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R12R13_32, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R14R15, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R12R13, left.type.isSigned)
} else {
// flip operands
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R14R15_32, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R12R13_32, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(left, RegisterOrPair.R14R15, left.type.isSigned)
assignmentAsmGen.assignExpressionToRegister(right, RegisterOrPair.R12R13, left.type.isSigned)
}
asmgen.out("""
sec
@@ -1938,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")
}
}
}
@@ -386,7 +386,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
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_32, expr.type.isSigned)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
cmp #$${hex.substring(6, 8)}
@@ -424,7 +424,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
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_32, expr.type.isSigned)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15, expr.type.isSigned)
asmgen.out("""
lda cx16.r14
cmp $varname2
@@ -492,15 +492,15 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+3
bne $falseLabel""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
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_32, 1)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG | bne $falseLabel")
}
}
@@ -517,15 +517,15 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+3
beq $falseLabel""")
} else {
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, expr.type.isSigned)
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_32, 1)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out(" lda P8ZP_SCRATCH_REG | beq $falseLabel")
}
}
@@ -412,10 +412,9 @@ 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}"
@@ -425,7 +424,7 @@ internal class ProgramAndVarsGen(
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")
@@ -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
//
@@ -191,7 +192,7 @@ internal class AnyExprAsmGen(
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
@@ -118,7 +118,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
in combinedLongRegisters -> {
in CombinedLongRegisters -> {
val dt = if(signed) DataType.LONG
else
TODO("unsigned long $pos")
@@ -296,17 +296,7 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
elementDt.isLong -> {
// it's not an expression so no need to preserve R14-R15
asmgen.out("""
lda $arrayVarName+$indexValue
sta cx16.r14
lda $arrayVarName+$indexValue+1
sta cx16.r14+1
lda $arrayVarName+$indexValue+2
sta cx16.r14+2
lda $arrayVarName+$indexValue+3
sta cx16.r14+3""")
assignRegisterLong(assign.target, RegisterOrPair.R14R15_32)
assignVariableLong(assign.target, "$arrayVarName+$indexValue", DataType.LONG)
}
elementDt.isFloat -> {
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
@@ -328,18 +318,7 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(assign.target, RegisterOrPair.AX)
}
elementDt.isLong -> {
// it's not an expression so no need to preserve R14-R15
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
asmgen.out("""
lda $arrayVarName,y
sta cx16.r14
lda $arrayVarName+1,y
sta cx16.r14+1
lda $arrayVarName+2,y
sta cx16.r14+2
lda $arrayVarName+3,y
sta cx16.r14+3""")
assignRegisterLong(assign.target, RegisterOrPair.R14R15_32)
assignVariableLongIndexed(assign.target, arrayVarName, value)
}
elementDt.isFloat -> {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.A)
@@ -374,7 +353,44 @@ internal class AssignmentAsmGen(
assignMemoryByte(target, null, address)
return
}
else if(address is PtAddressOf) {
if(address.identifier!=null) {
if(asmgen.isZpVar(address.identifier!!)) {
asmgen.loadIndirectByte(asmgen.asmVariableName(address.identifier!!), 0u)
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
} else if(address.dereference!=null) {
val (zpPtrVar, offset) = pointergen.deref(address.dereference!!, false)
asmgen.loadIndirectByte(zpPtrVar, offset)
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
asmgen.assignExpressionToVariable(address, "P8ZP_SCRATCH_PTR", DataType.UWORD)
asmgen.loadIndirectByte("P8ZP_SCRATCH_PTR", 0u)
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
else if (address is PtBinaryExpression) {
val result = asmgen.pointerViaIndexRegisterPossible(address)
if(result!=null) {
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
if(addressOfIdentifier!=null) {
val varname = asmgen.asmVariableName(addressOfIdentifier)
if(result.second is PtNumber) {
val offset = (result.second as PtNumber).number.toInt()
asmgen.out(" lda $varname+$offset")
assignRegisterByte(target, CpuRegister.A, false, true)
return
} else if (result.second is PtIdentifier) {
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
asmgen.out(" ldx $offsetname | lda $varname,x")
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
}
}
if(asmgen.tryOptimizedPointerAccessWithA(address, false)) {
assignRegisterByte(target, CpuRegister.A, false, true)
return
@@ -546,7 +562,7 @@ internal class AssignmentAsmGen(
dt.isLong -> {
TODO("read long ${value.position}")
}
else -> throw AssemblyError("unsupported dereference type ${dt} ${value.position}")
else -> throw AssemblyError("unsupported dereference type $dt ${value.position}")
}
}
else {
@@ -659,6 +675,43 @@ internal class AssignmentAsmGen(
}
private fun assignBuiltinFunctionCall(target: AsmAssignTarget, value: PtBuiltinFunctionCall) {
if(value.name=="peekl" && target.kind==TargetStorageKind.VARIABLE) {
val arg = value.args[0]
if(arg is PtNumber) {
val address = arg.number.toInt()
asmgen.out("""
lda $address
sta ${target.asmVarname}
lda $address+1
sta ${target.asmVarname}+1
lda $address+2
sta ${target.asmVarname}+2
lda $address+3
sta ${target.asmVarname}+3""")
return
}
else if(arg is PtIdentifier) {
val varname = asmgen.asmVariableName(arg)
if(asmgen.isZpVar(arg)) {
asmgen.out("""
ldy #0
lda ($varname),y
sta ${target.asmVarname}
iny
lda ($varname),y
sta ${target.asmVarname}+1
iny
lda ($varname),y
sta ${target.asmVarname}+2
iny
lda ($varname),y
sta ${target.asmVarname}+3""")
return
}
}
}
val returnDt = asmgen.translateBuiltinFunctionCallExpression(value, target.register)
if(target.register==null) {
// still need to assign the result to the target variable/etc.
@@ -671,7 +724,7 @@ internal class AssignmentAsmGen(
}
returnDt== BaseDataType.LONG -> {
// longs are in R14:R15 (r14=lsw, r15=msw)
assignRegisterLong(target, RegisterOrPair.R14R15_32)
assignRegisterLong(target, RegisterOrPair.R14R15)
}
returnDt==BaseDataType.FLOAT -> {
// float result from function sits in FAC1
@@ -726,7 +779,7 @@ internal class AssignmentAsmGen(
RegisterOrPair.AY -> assignVirtualRegister(target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignVirtualRegister(target, RegisterOrPair.XY)
in Cx16VirtualRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
in combinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
in CombinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
else -> {
val sflag = returnValue.first.statusflag
if(sflag!=null)
@@ -780,7 +833,7 @@ internal class AssignmentAsmGen(
}
target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
target.datatype.isLong -> {
require(register in combinedLongRegisters)
require(register in CombinedLongRegisters)
assignRegisterLong(target, register)
}
else -> throw AssemblyError("expected byte or word")
@@ -804,7 +857,7 @@ internal class AssignmentAsmGen(
true
else {
if(expr.type.isLong && expr.operator in "*/%")
TODO("long multiplication or division ${expr.position}")
TODO("long multiplication or division - use floats for now ${expr.position} ")
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
}
@@ -1199,7 +1252,6 @@ internal class AssignmentAsmGen(
}
}
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
if(expr.definingBlock()!!.options.veraFxMuls){
// cx16 verafx hardware mul
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")
@@ -1282,10 +1334,10 @@ internal class AssignmentAsmGen(
}
// TODO: cannot preserve R14:R15 on the stack here because the stack already contains a value that we need to pop
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, signed)
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, signed)
asmgen.out(" pla | sta P8ZP_SCRATCH_REG")
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "P8ZP_SCRATCH_REG")
assignRegisterLong(target, RegisterOrPair.R14R15_32)
assignRegisterLong(target, RegisterOrPair.R14R15)
return true
}
}
@@ -1421,13 +1473,13 @@ internal class AssignmentAsmGen(
return true
}
if(target.register!=RegisterOrPair.R14R15_32)
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, signed)
if(target.register!=RegisterOrPair.R14R15)
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, signed)
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r14", expr.operator, shifts)
assignRegisterLong(target, RegisterOrPair.R14R15_32)
if(target.register!=RegisterOrPair.R14R15_32)
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
assignRegisterLong(target, RegisterOrPair.R14R15)
if(target.register!=RegisterOrPair.R14R15)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
return true
}
}
@@ -1670,23 +1722,55 @@ internal class AssignmentAsmGen(
}
} else {
if(expr.operator=="+") {
asmgen.out("""
clc
adc #<${right.number.toHex()}
tax
tya
adc #>${right.number.toHex()}
tay
txa""")
if(right.number.toInt() and 255 == 0) {
// can add only to the msb, which is in Y
if(right.number==256.0)
asmgen.out(" iny")
else if(right.number==512.0)
asmgen.out(" iny | iny")
else
asmgen.out("""
tax
tya
clc
adc #>${right.number.toHex()}
tay
txa""")
} else {
asmgen.out("""
clc
adc #<${right.number.toHex()}
tax
tya
adc #>${right.number.toHex()}
tay
txa""")
}
} else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #<${right.number.toHex()}
tax
tya
sbc #>${right.number.toHex()}
tay
txa""")
if(right.number.toInt() and 255 == 0) {
// can sub only from the msb, which is in Y
if(right.number==256.0)
asmgen.out(" dey")
else if(right.number==512.0)
asmgen.out(" dey | dey")
else
asmgen.out("""
tax
tya
sec
sbc #>${right.number.toHex()}
tay
txa""")
} else {
asmgen.out("""
sec
sbc #<${right.number.toHex()}
tax
tya
sbc #>${right.number.toHex()}
tay
txa""")
}
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
@@ -1832,11 +1916,11 @@ internal class AssignmentAsmGen(
return true
} else if(target.kind==TargetStorageKind.POINTER) {
// not an expression, no need to save R14/R15
assignExpressionToRegister(expr, RegisterOrPair.R14R15_32, target.datatype.isSigned)
assignExpressionToRegister(expr, RegisterOrPair.R14R15, target.datatype.isSigned)
pointergen.assignLongVar(target.pointer!!, "cx16.r14")
return true
} else {
TODO("add/subtract long const into ${target.kind} ${target.position} - use simple expressions and temporary variables for now")
TODO("add/subtract long const into ${target.kind} ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
}
}
is PtIdentifier -> {
@@ -1877,8 +1961,8 @@ internal class AssignmentAsmGen(
return true
} else {
// isn't an expression, so no need to preserve R12-R15
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13_32, left.type.isSigned)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15_32, left.type.isSigned)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, left.type.isSigned)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, left.type.isSigned)
if(expr.operator=="+") {
asmgen.out("""
clc
@@ -1910,24 +1994,24 @@ internal class AssignmentAsmGen(
sbc cx16.r14+3
sta cx16.r12+3""")
}
asmgen.assignRegister(RegisterOrPair.R12R13_32, target)
asmgen.assignRegister(RegisterOrPair.R12R13, target)
return true
}
}
else -> {
val targetreg = target.register
if(targetreg==RegisterOrPair.R14R15_32)
asmgen.pushLongRegisters(RegisterOrPair.R12R13_32, 1)
if(targetreg==RegisterOrPair.R14R15)
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
else
asmgen.pushLongRegisters(RegisterOrPair.R12R13_32, 2)
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, expr.left.type.isSigned)
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13_32, expr.right.type.isSigned)
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 2)
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
assignRegisterLong(target, RegisterOrPair.R14R15_32)
if(targetreg==RegisterOrPair.R14R15_32)
asmgen.popLongRegisters(RegisterOrPair.R12R13_32, 1)
assignRegisterLong(target, RegisterOrPair.R14R15)
if(targetreg==RegisterOrPair.R14R15)
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
else
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 2)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 2)
return true
}
}
@@ -2094,10 +2178,10 @@ internal class AssignmentAsmGen(
}
val targetreg = target.register
if(targetreg!=RegisterOrPair.R14R15_32) {
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
if(targetreg!=RegisterOrPair.R14R15) {
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
}
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R14R15_32, expr.left.type.isSigned)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
val constval = expr.right.asConstInteger()
val varname = (expr.right as? PtIdentifier)?.name
if(constval!=null)
@@ -2108,9 +2192,9 @@ internal class AssignmentAsmGen(
// TODO: preserve R14:R15 on stack here? (the function does't do it for us)
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r14", expr.operator, expr.right)
}
assignRegisterLong(target, RegisterOrPair.R14R15_32)
if(targetreg!=RegisterOrPair.R14R15_32) {
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
assignRegisterLong(target, RegisterOrPair.R14R15)
if(targetreg!=RegisterOrPair.R14R15) {
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
}
return true
}
@@ -2621,15 +2705,15 @@ $endLabel""")
}
valueDt.isByte -> {
assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base, target.position)
}
valueDt.isLong -> {
assignExpressionToRegister(value, RegisterOrPair.R14R15_32, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15_32, valueDt.base)
assignExpressionToRegister(value, RegisterOrPair.R14R15, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15, valueDt.base, target.position)
}
valueDt.isWord || valueDt.isPointer -> {
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base, target.position)
}
valueDt.isFloat -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
@@ -2669,7 +2753,7 @@ $endLabel""")
assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
return
}
in combinedLongRegisters -> {
in CombinedLongRegisters -> {
assignExpressionToRegister(value, RegisterOrPair.A, false)
assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
return
@@ -2695,37 +2779,37 @@ $endLabel""")
// 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
}
RegisterOrPair.R0R1_32 -> {
RegisterOrPair.R0R1 -> {
assignExpressionToRegister(value, RegisterOrPair.R0, false)
asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
}
RegisterOrPair.R2R3_32 -> {
RegisterOrPair.R2R3 -> {
assignExpressionToRegister(value, RegisterOrPair.R2, false)
asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
}
RegisterOrPair.R4R5_32 -> {
RegisterOrPair.R4R5 -> {
assignExpressionToRegister(value, RegisterOrPair.R4, false)
asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
}
RegisterOrPair.R6R7_32 -> {
RegisterOrPair.R6R7 -> {
assignExpressionToRegister(value, RegisterOrPair.R6, false)
asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
}
RegisterOrPair.R8R9_32 -> {
RegisterOrPair.R8R9 -> {
assignExpressionToRegister(value, RegisterOrPair.R8, false)
asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
}
RegisterOrPair.R10R11_32 -> {
RegisterOrPair.R10R11 -> {
assignExpressionToRegister(value, RegisterOrPair.R10, false)
asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
}
RegisterOrPair.R12R13_32 -> {
RegisterOrPair.R12R13 -> {
assignExpressionToRegister(value, RegisterOrPair.R12, false)
asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
}
RegisterOrPair.R14R15_32 -> {
RegisterOrPair.R14R15 -> {
assignExpressionToRegister(value, RegisterOrPair.R14, false)
asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
}
else -> {}
}
@@ -2825,6 +2909,24 @@ $endLabel""")
assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" jsr floats.GIVAYFAY")
}
valueDt.isLong -> {
when(value) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(value)
asmgen.out("""
lda #<$varname
ldy #>$varname
jsr floats.internal_long_AY_to_FAC""")
}
else -> {
assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
asmgen.out("""
lda #<cx16.r14
ldy #>cx16.r14
jsr floats.internal_long_AY_to_FAC""")
}
}
}
else -> throw AssemblyError("invalid dt at ${target.position}")
}
if(target.register==RegisterOrPair.FAC2) {
@@ -2856,11 +2958,11 @@ $endLabel""")
}
is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}")
else -> {
asmgen.pushLongRegisters(RegisterOrPair.R14R15_32, 1)
assignExpressionToRegister(value, RegisterOrPair.R14R15_32, true)
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
asmgen.out(" lda cx16.r14")
assignRegisterByte(target, CpuRegister.A, true, false)
asmgen.popLongRegisters(RegisterOrPair.R14R15_32, 1)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
}
}
}
@@ -3047,7 +3149,6 @@ $endLabel""")
BaseDataType.UWORD, BaseDataType.POINTER -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
}
BaseDataType.POINTER -> TODO("cast to pointer")
BaseDataType.LONG -> {
asmgen.out("""
lda $sourceAsmVarName
@@ -3077,7 +3178,14 @@ $endLabel""")
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName")
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
BaseDataType.LONG -> TODO("cast float to long")
BaseDataType.LONG -> {
asmgen.out("""
sta cx16.r0L
sty cx16.r0H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.cast_as_long""")
}
else -> throw AssemblyError("weird type")
}
}
@@ -3087,6 +3195,15 @@ $endLabel""")
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
} else if(targetDt.isWord || targetDt.isPointer) {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
} else if(targetDt.isFloat) {
asmgen.out("""
lda #<$sourceAsmVarName
ldy #>$sourceAsmVarName
sta cx16.r1L
sty cx16.r1H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.internal_long_R1_to_float_AY""")
} else
throw AssemblyError("weird type")
}
@@ -3096,7 +3213,7 @@ $endLabel""")
private fun assignTypeCastedRegisters(targetAsmVarName: String, targetDt: BaseDataType,
regs: RegisterOrPair, sourceDt: BaseDataType) {
regs: RegisterOrPair, sourceDt: BaseDataType, position: Position) {
if(sourceDt == targetDt)
throw AssemblyError("typecast to identical type")
@@ -3119,13 +3236,12 @@ $endLabel""")
BaseDataType.BYTE -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
BaseDataType.UWORD, BaseDataType.WORD -> {
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.POINTER -> TODO("cast to pointer")
BaseDataType.LONG -> {
asmgen.out("""
st${regs.toString().lowercase()} $targetAsmVarName
@@ -3153,7 +3269,7 @@ $endLabel""")
}
BaseDataType.BYTE -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign byte to bool")
BaseDataType.BOOL -> TODO("assign byte to bool $position")
BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
@@ -3163,7 +3279,7 @@ $endLabel""")
else
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.WORD -> {
BaseDataType.WORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" txa")
@@ -3173,7 +3289,6 @@ $endLabel""")
asmgen.signExtendAYlsb(sourceDt)
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
}
BaseDataType.POINTER -> TODO("cast to pointer")
BaseDataType.LONG -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
asmgen.signExtendLongVariable(targetAsmVarName, sourceDt)
@@ -3197,11 +3312,11 @@ $endLabel""")
}
BaseDataType.UWORD -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign uword to bool")
BaseDataType.BOOL -> TODO("assign uword to bool $position")
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
BaseDataType.WORD -> {
BaseDataType.WORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
@@ -3209,7 +3324,6 @@ $endLabel""")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.POINTER -> TODO("cast to pointer")
BaseDataType.LONG -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
@@ -3236,11 +3350,11 @@ $endLabel""")
}
BaseDataType.WORD -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign word to bool")
BaseDataType.BOOL -> TODO("assign word to bool $position")
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
BaseDataType.UWORD -> {
BaseDataType.UWORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
@@ -3248,7 +3362,6 @@ $endLabel""")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.POINTER -> TODO("cast to pointer")
BaseDataType.LONG -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
@@ -3300,10 +3413,21 @@ $endLabel""")
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName")
} else if(targetDt.isWord || targetDt.isPointer) {
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName | lda cx16.$startreg+1 | sta $targetAsmVarName+1")
} else if(targetDt.isFloat) {
if(regs==RegisterOrPair.R0R1)
throw AssemblyError("cannot assign long in R0R1 to float because r1 is used as a parameter $position")
asmgen.out("""
lda #<cx16.$startreg
ldy #>cx16.$startreg
sta cx16.r1L
sty cx16.r1H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.internal_long_R1_to_float_AY""")
} else
throw AssemblyError("weird type")
throw AssemblyError("weird type $targetDt")
}
else -> throw AssemblyError("weird type")
else -> throw AssemblyError("weird type $sourceDt")
}
}
@@ -3517,9 +3641,8 @@ $endLabel""")
lda $varName+3
sta ${target.asmVarname}+3,y""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in combinedLongRegisters)
require(target.register in CombinedLongRegisters)
val regstart = target.register!!.startregname()
when(sourceDt) {
DataType.BYTE -> {
@@ -3568,6 +3691,59 @@ $endLabel""")
}
}
TargetStorageKind.POINTER -> pointergen.assignLongVar(target.pointer!!, varName)
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
private fun assignVariableLongIndexed(target: AsmAssignTarget, arrayVarName: String, index: PtArrayIndexer) {
asmgen.loadScaledArrayIndexIntoRegister(index, CpuRegister.Y)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $arrayVarName,y
sta ${target.asmVarname}
lda $arrayVarName+1,y
sta ${target.asmVarname}+1
lda $arrayVarName+2,y
sta ${target.asmVarname}+2
lda $arrayVarName+3,y
sta ${target.asmVarname}+3""")
}
TargetStorageKind.REGISTER -> {
require(target.register in CombinedLongRegisters)
val regstart = target.register!!.startregname()
asmgen.out("""
lda $arrayVarName,y
sta cx16.$regstart
lda $arrayVarName+1,y
sta cx16.$regstart+1
lda $arrayVarName+2,y
sta cx16.$regstart+2
lda $arrayVarName+3,y
sta cx16.$regstart+3""")
}
TargetStorageKind.ARRAY -> {
val deref = target.array!!.pointerderef
if(deref!=null) {
pointergen.assignLongVar(IndexedPtrTarget(target), arrayVarName)
return
}
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.X)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out("""
lda $arrayVarName,y
sta ${target.asmVarname},x
lda $arrayVarName+1,y
sta ${target.asmVarname}+1,x
lda $arrayVarName+2,y
sta ${target.asmVarname}+2,x
lda $arrayVarName+3,y
sta ${target.asmVarname}+3,x""")
}
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index, DataType.arrayFor(BaseDataType.LONG))
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -3669,7 +3845,7 @@ $endLabel""")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().lowercase()}+1")
}
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
} else {
@@ -3684,7 +3860,7 @@ $endLabel""")
lda $varName+1
sta cx16.${target.register.toString().lowercase()}+1""")
}
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
}
@@ -3863,7 +4039,7 @@ $endLabel""")
lda #0
sta cx16.${target.register.toString().lowercase()}+1""")
}
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
else -> throw AssemblyError("weird register")
}
}
@@ -4058,7 +4234,7 @@ $endLabel""")
internal fun assignRegisterLong(target: AsmAssignTarget, pairedRegisters: RegisterOrPair) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(pairedRegisters in combinedLongRegisters) {
if(pairedRegisters in CombinedLongRegisters) {
val startreg = pairedRegisters.startregname()
asmgen.out("""
lda cx16.$startreg
@@ -4089,7 +4265,7 @@ $endLabel""")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
val targetreg = target.register!!
require(targetreg in combinedLongRegisters && pairedRegisters in combinedLongRegisters)
require(targetreg in CombinedLongRegisters && pairedRegisters in CombinedLongRegisters)
if(targetreg!=pairedRegisters) {
val sourceStartReg = pairedRegisters.startregname()
val targetStartReg = targetreg.startregname()
@@ -4209,7 +4385,7 @@ $endLabel""")
if(extendSignedBits)
extendToMSBofVirtualReg(CpuRegister.A, reg, signed)
}
in combinedLongRegisters -> {
in CombinedLongRegisters -> {
val reg = target.register.startregname()
asmgen.out(" sta cx16.$reg")
if(extendSignedBits) {
@@ -4267,7 +4443,7 @@ $endLabel""")
if(extendSignedBits)
extendToMSBofVirtualReg(CpuRegister.X, reg, signed)
}
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
else -> throw AssemblyError("weird register")
}
CpuRegister.Y -> when(target.register!!) {
@@ -4321,7 +4497,7 @@ $endLabel""")
if(extendSignedBits)
extendToMSBofVirtualReg(CpuRegister.Y, reg, signed)
}
in combinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
in CombinedLongRegisters -> TODO("assign byte to long reg ${target.position}")
else -> throw AssemblyError("weird register")
}
}
@@ -4597,10 +4773,10 @@ $endLabel""")
TargetStorageKind.REGISTER -> {
val startreg = target.register!!.startregname()
asmgen.out("""
stz $startreg
stz $startreg+1
stz $startreg+2
stz $startreg+3""")
stz cx16.$startreg
stz cx16.$startreg+1
stz cx16.$startreg+2
stz cx16.$startreg+3""")
}
TargetStorageKind.POINTER -> pointergen.assignLong(target.pointer!!, 0)
TargetStorageKind.VOID -> { /* do nothing */ }
@@ -4643,7 +4819,7 @@ $endLabel""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in combinedLongRegisters)
require(target.register in CombinedLongRegisters)
val regstart = target.register!!.startregname()
val hex = long.toLongHex()
asmgen.out("""
@@ -4993,7 +5169,7 @@ $endLabel""")
lda #0
sta cx16.${target.register.toString().lowercase()}+1""")
}
in combinedLongRegisters -> TODO("assign memory byte into long ${target.position}")
in CombinedLongRegisters -> TODO("assign memory byte into long ${target.position}")
else -> throw AssemblyError("weird register")
}
TargetStorageKind.POINTER -> pointergen.assignByteMemory(PtrTarget(target), address)
@@ -5029,7 +5205,7 @@ $endLabel""")
lda #0
sta cx16.${target.register.toString().lowercase()}+1""")
}
in combinedLongRegisters -> {
in CombinedLongRegisters -> {
val startreg = target.register.startregname()
asmgen.out("""
sta cx16.$startreg
@@ -5143,6 +5319,23 @@ $endLabel""")
asmgen.storeAIntoPointerVar(addressExpr)
}
addressExpr is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addressExpr)
if(result!=null) {
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
if(addressOfIdentifier!=null) {
val varname = asmgen.asmVariableName(addressOfIdentifier)
if(result.second is PtNumber) {
val offset = (result.second as PtNumber).number.toInt()
asmgen.out(" sta $varname+$offset")
return
} else if (result.second is PtIdentifier) {
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
asmgen.out(" ldx $offsetname | sta $varname,x")
return
}
}
}
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
storeViaExprEval()
}
@@ -5252,7 +5445,7 @@ $endLabel""")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
in combinedLongRegisters -> TODO("in place negate long invert ${target.position}")
in CombinedLongRegisters -> TODO("in place negate long invert ${target.position}")
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
@@ -5326,7 +5519,7 @@ $endLabel""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), ignoreDatatype, scope)
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), scope)
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -5381,13 +5574,13 @@ $endLabel""")
tay""")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
in combinedLongRegisters -> TODO("in place negate long reg ${target.position}")
in CombinedLongRegisters -> TODO("in place negate long reg ${target.position}")
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), ignoreDatatype, scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), scope)
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -5411,8 +5604,14 @@ $endLabel""")
}
TargetStorageKind.ARRAY -> TODO(" - long array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register negate ${target.position}")
TargetStorageKind.POINTER -> TODO("long negate via pointer ${target.position}")
TargetStorageKind.REGISTER -> {
val regstart = target.register!!.startregname()
asmgen.out("""
lda #<cx16.$regstart
ldy #>cx16.$regstart
jsr prog8_lib.long_negate_inplace""")
}
TargetStorageKind.POINTER -> pointergen.inplaceLongNegate(PtrTarget(target), scope)
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -113,9 +113,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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 -> TODO("32 bits register inplace modification? ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace modify long with array ${target.position}")
SourceStorageKind.MEMORY -> TODO("memread into long ${target.position}")
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 -> {
@@ -124,7 +124,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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 ${target.position}")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(target.asmVarname, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -164,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 ${target.position}")
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!!)
}
}
}
@@ -309,14 +309,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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 modifiication ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modifiication ${target.position}")
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 modifiication ${target.position}")
TODO("inplace long modification ${target.position}")
} else {
TODO("inplace long modifiication ${target.position}")
TODO("inplace long modification ${target.position}")
}
}
}
@@ -328,7 +328,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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 ${target.position}")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(targetVarName, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -519,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 ${target.position}")
SourceStorageKind.MEMORY -> inplacemodificationFloatWithValue(tempvar, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
@@ -555,8 +555,15 @@ 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
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15_32, value.type.isSigned)
// 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")
}
@@ -711,13 +718,21 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
lda $variable
sta $variable+1
lda #0
sta $variable
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("""
@@ -738,12 +753,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sta $variable+2
lda #0
sta $variable
sta $variable+1
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("""
@@ -756,12 +776,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
value <= 31 -> {
val shift = value-24
asmgen.out("""
if(shift==1) {
asmgen.out(" lda $variable | asl a")
} else {
asmgen.out("""
lda $variable
ldy #$shift
- asl a
dey
bne -
bne -""")
}
asmgen.out("""
sta $variable+3
lda #0
sta $variable
@@ -792,7 +817,45 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
ror $variable""")
}
}
// TODO optimize for more cases 8, 16, 24 etc but don't forget to take the sign bit into account!
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
@@ -885,41 +948,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
1 -> {
asmgen.out("""
lda $variable
bne +
dec $variable+1
bne +
dec $variable+2
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("""
lda $variable
sec
lda $variable
sbc #$value
sta $variable
bcs +
dec $variable+1
bne +
dec $variable+2
bne +
dec $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
lda $variable
sec
sbc #<$value
sta $variable
bcs +++
lda $variable+1
sbc #>$value
sta $variable+1
bcs +
dec $variable+2
bne ++
lda $variable+2
bne +
dec $variable+3
+ dec $variable+2
+ dec $variable+1
+""")
} else {
val hex = value.toLongHex()
@@ -1386,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 ${pointervar.position}")
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 ${pointervar.position}")
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()) {
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -66,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()
@@ -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())
@@ -76,7 +76,7 @@ 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 -> {
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)
@@ -191,7 +191,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
"&=" -> 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)
">>=" -> {
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)
@@ -557,7 +560,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
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 {
@@ -567,18 +570,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
}
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)
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)
return result
}
}
val tr = expressionEval.translateExpression(memory!!.address)
@@ -58,12 +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])
@@ -87,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 {
@@ -428,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])) {
@@ -445,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) {
@@ -459,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])
@@ -555,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 {
@@ -564,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)
}
@@ -708,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 {
@@ -718,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)
}
}
@@ -771,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)
@@ -782,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)
}
}
@@ -792,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)
@@ -803,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)
}
}
@@ -827,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
}
}
@@ -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)
@@ -819,6 +807,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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}")
}
}
@@ -837,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}")
}
}
@@ -932,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)
@@ -953,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)
@@ -1846,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)
}
}
@@ -1859,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 {
@@ -1868,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)
@@ -47,10 +48,12 @@ class IRCodeGen(
ensureFirstChunkLabels(irProg)
irProg.linkChunks()
irProg.convertAsmChunks()
irProg.splitSSAchunks()
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()
@@ -1978,7 +1981,7 @@ class IRCodeGen(
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
in combinedLongRegisters -> {
in CombinedLongRegisters -> {
require(paramDt==IRDataType.LONG)
val startreg = registerOrFlag.registerOrPair!!.startregname()
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
@@ -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 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
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,7 +304,7 @@ 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 OpcodesThatBranchUnconditionally) {
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
chunk.instructions.removeAt(idx)
changed = true
}
@@ -309,7 +313,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// replace subsequent opcodes that jump by just the first
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatBranchUnconditionally) {
if(previous.opcode in OpcodesThatBranchUnconditionally && idx<chunk.instructions.size) {
chunk.instructions.removeAt(idx)
changed = true
}
@@ -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) {
@@ -440,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
}
@@ -505,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
}
}
@@ -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())
+1 -1
View File
@@ -64,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)
}
@@ -49,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
}
@@ -68,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
@@ -102,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
@@ -126,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
@@ -142,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
@@ -165,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
}
@@ -176,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
@@ -196,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
}
@@ -211,7 +211,7 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=42, immediate = -1)
))
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg)
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 6
+10 -10
View File
@@ -44,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)
@@ -163,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)
@@ -235,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)
@@ -303,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)
@@ -341,7 +341,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 2
irChunks.size shouldBe 1
}
test("integer comparison expressions against zero") {
@@ -359,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)
@@ -431,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)
@@ -499,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)
@@ -537,7 +537,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 2
irChunks.size shouldBe 1
}
test("extsub allowed in ir-codegen") {
@@ -548,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)
@@ -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
@@ -109,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
}
}
@@ -362,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
@@ -541,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(
@@ -39,7 +39,7 @@ class VarConstantValueTypeAdjuster(
// 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
@@ -203,7 +203,7 @@ class VarConstantValueTypeAdjuster(
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)
@@ -299,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
}
}
@@ -329,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) {
@@ -615,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) {
@@ -647,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
}
@@ -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)
}
}
+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.
+88 -261
View File
@@ -133,7 +133,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
@@ -143,7 +143,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
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
@@ -338,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
@@ -348,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
@@ -358,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: ----
@@ -366,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
}}
}
@@ -393,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)
@@ -448,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 --------
@@ -484,48 +545,6 @@ 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 {{
@@ -673,7 +692,7 @@ user_vector .word ?
%asm {{
sei
lda #0
sta $ff00 ; default bank 15
sta c128.MMUCR ; default bank 15
jmp (cbm.RESET_VEC)
}}
}
@@ -747,196 +766,6 @@ _larger
}
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.
%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 -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
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
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 {{
lda #$80
@@ -988,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
@@ -1056,7 +871,7 @@ _no_msb_size
}}
}
inline asmsub pushl(long value @R0R1_32) {
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
@@ -1069,7 +884,7 @@ _no_msb_size
}}
}
inline asmsub popl() -> long @R0R1_32 {
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
@@ -1140,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
@@ -1289,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
@@ -1302,6 +1127,7 @@ 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
@@ -1325,13 +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 #<$ff00
ldy #>$ff00
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
+35 -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
+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
+48 -250
View File
@@ -132,7 +132,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
@@ -142,7 +142,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
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
@@ -458,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 --------
@@ -490,48 +520,6 @@ 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 {{
@@ -756,196 +744,6 @@ _larger
}
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.
%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 -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
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
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 {{
lda #$80
@@ -997,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
@@ -1065,7 +849,7 @@ _no_msb_size
}}
}
inline asmsub pushl(long value @R0R1_32) {
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
@@ -1078,7 +862,7 @@ _no_msb_size
}}
}
inline asmsub popl() -> long @R0R1_32 {
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
@@ -1150,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
@@ -1313,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
@@ -1349,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
+22 -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
+79
View File
@@ -572,6 +572,85 @@ _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!
}}
}
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 %)
+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
+191 -103
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 {
@@ -809,6 +893,7 @@ io_error:
if cbm.READST()!=0
cx16.r0 = 0
}
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
return cx16.r0
}
@@ -999,15 +1084,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()
@@ -1016,13 +1098,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()
@@ -1030,54 +1109,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!
}}
}
+2
View File
@@ -1,5 +1,7 @@
%import syslib
; NOTE: deprecated, use psg2 module instead. That fixes timing and has other benefits.
psg {
%option ignore_unused
+216
View File
@@ -0,0 +1,216 @@
; Vera PSG abstraction module, version 2
; Supersedes (and is not compatible to) the previous psg module which had some problems (timings were not consistent, etc)
; This new module requires setting up the voice parameters in their Voice structure,
; and then calling the update() routine periodically to apply the changes.
; You can read and write all parameters in the Voice structure as desired.
; For efficiency reasons, the volume envelope state is not kept in there though.
; Vera PSG registers:
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
; 00 frequency word LSB
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
; 02 bit 7 =right, bit 6 = left, bits 5-0 = volume 0-63 levels
; 03 bit 7,6 = waveform, bits 5-0 = Pulse width 0-63
; waveform: 0=pulse, 1=sawtooth, 2=triangle, 3=noise
psg2 {
%option ignore_unused
; waveform options:
const ubyte PULSE = %00000000
const ubyte SQUARE = %00000000 ; same as PULSE
const ubyte SAWTOOTH = %01000000
const ubyte TRIANGLE = %10000000
const ubyte NOISE = %11000000
; channels:
const ubyte LEFT = %01000000
const ubyte RIGHT = %10000000
const ubyte BOTH = %11000000
const ubyte DISABLED = %00000000
; envelope states:
const ubyte E_OFF = 0
const ubyte E_ATTACK = 1
const ubyte E_SUSTAIN = 2
const ubyte E_RELEASE = 3
struct Voice {
ubyte channels ; LEFT,RIGHT,BOTH or DISABLED
ubyte volume ; 0-63, use setvolume() to adjust this if you are also using ADSR envelope
ubyte waveform ; PULSE/SQUARE,SAWTOOTH,TRIANGLE,NOISE
ubyte pulsewidth ; 0-63
uword frequency
}
; envelope parameters are kept in arrays for maximum efficiency
ubyte[16] envelope_states
ubyte[16] envelope_attacks
ubyte[16] envelope_sustains
ubyte[16] envelope_releases
ubyte[16] envelope_maxvolumes
uword[16] envelope_volumes ; 8.8 fixed point (scaled by 256)
^^Voice voices = memory("psg2voices", 16*sizeof(Voice), 0) ; can't use array of structs so use mem block
^^Voice vptr
sub init() {
; -- required before using this module; initializes all registers to default (off) values
sys.irqsafe_set_irqd()
sys.memset(voices, 16*sizeof(Voice), 0)
sys.irqsafe_clear_irqd()
for cx16.r0L in 0 to 15
envelope_states[cx16.r0L] = E_OFF
}
sub off() {
; -- turn off all voices immediately
sys.irqsafe_set_irqd()
vptr = voices
for cx16.r0L in 0 to 15 {
vptr.channels = DISABLED
vptr++
envelope_states[cx16.r0L] = E_OFF
}
sys.irqsafe_clear_irqd()
void update()
}
sub voice(ubyte voice_num, ubyte channels, ubyte volume, ubyte waveform, ubyte pulsewidth) {
; -- sets all parameters for a single voice (frequency is unchanged - set that separately)
if voice_num > 15
return
volume &= %00111111
envelope_states[voice_num] = E_OFF
sys.irqsafe_set_irqd()
vptr = &voices[voice_num]
vptr.channels = channels
vptr.volume = volume
vptr.waveform = waveform
vptr.pulsewidth = pulsewidth & %00111111
; do not change frequency, user is expected to call frequency() separately
sys.irqsafe_clear_irqd()
envelope_volumes[voice_num] = mkword(volume, 0)
envelope_maxvolumes[voice_num] = volume
}
sub frequency(ubyte voice_num, uword @nozp freq) {
; -- sets new frequence for this voice
vptr = &voices[voice_num]
vptr.frequency = freq
}
sub volume(ubyte voice_num, ubyte @nozp vol) {
; -- Modifies the volume of this voice, adjusting the envelope as needed.
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
envelope_states[voice_num] = E_OFF
vptr = &voices[voice_num]
vptr.volume = vol
envelope_volumes[voice_num] = mkword(vol, 0)
envelope_maxvolumes[voice_num] = vol
}
sub envelope(ubyte voice_num, ubyte @nozp attack, ubyte @nozp sustain, ubyte @nozp release) {
; -- sets ASR envelope parameters for this voice
envelope_states[voice_num] = E_OFF
vptr = &voices[voice_num]
vptr.volume = 0
envelope_attacks[voice_num] = attack
envelope_sustains[voice_num] = sustain
envelope_releases[voice_num] = release
envelope_volumes[voice_num] = 0
envelope_states[voice_num] = E_ATTACK
}
sub getvoice(ubyte @nozp voice_num) -> ^^Voice {
; -- returns pointer to voice parameters
return &voices[voice_num]
}
sub update() -> bool {
; -- update adsr envelopes, then write all 16 voices to Vera PSG
; This has to be called every time you want to apply changed psg voice parameters.
; If you want to use real-time volume envelopes (Attack-Sustain-Release),
; you have to call this routine every 1/60th second, for example from your vsync irq handler.
; Or just install this routine as the only irq handler if you don't have to do other things there.
sys.push(cx16.r0L)
sys.pushw(cx16.r1)
sys.pushw(cx16.r2)
sys.pushw(cx16.r3)
vptr = voices
alias voice = cx16.r0L
alias maxvolume = cx16.r1
alias newvolume = cx16.r2
alias currentvolume = cx16.r3
; update active envelopes
for voice in 0 to 15 {
when envelope_states[voice] {
E_ATTACK -> {
; while current volume is less than max volume, increase volume by maxvolume * (attackspeed /256.0)
maxvolume = mkword(envelope_maxvolumes[voice], 0)
currentvolume = envelope_volumes[voice]
if currentvolume < maxvolume {
newvolume = currentvolume + (maxvolume >> 8)*envelope_attacks[voice]
newvolume = min(newvolume, maxvolume)
envelope_volumes[voice] = newvolume
vptr.volume = msb(newvolume)
} else
envelope_states[voice] = E_SUSTAIN
}
E_SUSTAIN -> {
if envelope_sustains[voice] > 0
envelope_sustains[voice]--
else
envelope_states[voice] = E_RELEASE
}
E_RELEASE -> {
; while current volume is not zero, decrease volume by maxvolume * (releasespeed /256.0)
currentvolume = envelope_volumes[voice]
if currentvolume > 0 {
uword subtraction = (currentvolume>>8)*envelope_releases[voice]
if subtraction > currentvolume
newvolume = 0
else
newvolume = currentvolume - subtraction
envelope_volumes[voice] = newvolume
vptr.volume = msb(newvolume)
} else
envelope_states[voice] = E_OFF
}
}
vptr++
}
cx16.save_vera_context()
cx16.VERA_CTRL = 0
cx16.VERA_ADDR = lsw(cx16.VERA_PSG_BASE)
cx16.VERA_ADDR_H = %00010000 | msw(cx16.VERA_PSG_BASE)
vptr = voices
repeat 16 {
if vptr.channels != DISABLED {
cx16.VERA_DATA0 = lsb(vptr.frequency)
cx16.VERA_DATA0 = msb(vptr.frequency)
cx16.VERA_DATA0 = vptr.channels | (vptr.volume & %00111111)
cx16.VERA_DATA0 = vptr.waveform | (vptr.pulsewidth & %00111111)
} else {
unroll 4 cx16.VERA_DATA0 = 0
}
vptr++
}
cx16.restore_vera_context()
cx16.r3 = sys.popw()
cx16.r2 = sys.popw()
cx16.r1 = sys.popw()
cx16.r0L = sys.pop()
return true
}
}
+153 -269
View File
@@ -127,7 +127,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
@@ -137,7 +137,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
asmsub RDTIML() clobbers(X) -> long @R0R1 {
; -- like RDTIM_safe() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM_safe
@@ -217,6 +217,16 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; signed long versions
&long r0r1sl = $0002
&long r2r3sl = $0006
&long r4r5sl = $000a
&long r6r7sl = $000e
&long r8r9sl = $0012
&long r10r11sl = $0016
&long r12r13sl = $001a
&long r14r15sl = $001e
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
@@ -402,10 +412,9 @@ cx16 {
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
const long VERA_PSG_BASE = $1F9C0
const long VERA_PALETTE_BASE = $1FA00
const long VERA_SPRITES_BASE = $1FC00
; I/O
@@ -414,10 +423,13 @@ cx16 {
&ubyte via1pra = VIA1_BASE + 1
&ubyte via1ddrb = VIA1_BASE + 2
&ubyte via1ddra = VIA1_BASE + 3
&uword via1t1 = VIA1_BASE + 4
&ubyte via1t1l = VIA1_BASE + 4
&ubyte via1t1h = VIA1_BASE + 5
&uword via1t1lw = VIA1_BASE + 6
&ubyte via1t1ll = VIA1_BASE + 6
&ubyte via1t1lh = VIA1_BASE + 7
&uword via1t2 = VIA1_BASE + 8
&ubyte via1t2l = VIA1_BASE + 8
&ubyte via1t2h = VIA1_BASE + 9
&ubyte via1sr = VIA1_BASE + 10
@@ -427,15 +439,18 @@ cx16 {
&ubyte via1ier = VIA1_BASE + 14
&ubyte via1ora = VIA1_BASE + 15
const uword VIA2_BASE = $9f10 ;VIA 6522 #2
const uword VIA2_BASE = $9f10 ;VIA 6522 #2, not always present
&ubyte via2prb = VIA2_BASE + 0
&ubyte via2pra = VIA2_BASE + 1
&ubyte via2ddrb = VIA2_BASE + 2
&ubyte via2ddra = VIA2_BASE + 3
&uword via2t1 = VIA2_BASE + 4
&ubyte via2t1l = VIA2_BASE + 4
&ubyte via2t1h = VIA2_BASE + 5
&uword via2t1lw = VIA2_BASE + 6
&ubyte via2t1ll = VIA2_BASE + 6
&ubyte via2t1lh = VIA2_BASE + 7
&uword via2t2 = VIA2_BASE + 8
&ubyte via2t2l = VIA2_BASE + 8
&ubyte via2t2h = VIA2_BASE + 9
&ubyte via2sr = VIA2_BASE + 10
@@ -463,7 +478,7 @@ extsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobb
extsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
extsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
extsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
extsub $ff7d = PRIMM()
extsub $ff7d = PRIMM() ; print immediate string (the 0-terminated string immediately following the JSR PRIMM instruction)
; high level graphics & fonts
extsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
@@ -535,7 +550,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see joysticks_detect() and joysticks_getall()
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
extsub $C000 = x16edit_default() clobbers(A,X,Y)
@@ -543,6 +558,11 @@ extsub $C003 = x16edit_loadfile(ubyte firstbank @X, ubyte lastbank @Y, str filen
extsub $C006 = x16edit_loadfile_options(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
uword filenameLengthAndOptions @R1, uword tabstopAndWordwrap @R2,
uword disknumberAndColors @R3, uword headerAndStatusColors @R4) clobbers(A,X,Y)
; note: the following routine is not yet available in currently released ROMS (v49) hopefully the updated X16Edit will be in r50.
extsub $C009 = x16edit_loadfile_options2(ubyte firstbank @X, ubyte lastbank @Y, str filename @R0,
uword filenameLengthAndOptions @R1, uword tabstopAndWordwrap @R2,
uword disknumberAndColors @R3, uword headerAndStatusColors @R4,
uword linenumberLM @R5, ubyte linenumberH @R6) clobbers(A,X,Y)
; Audio (rom bank 10)
; NOTE: because these are auto-banked, you should not call them from an IRQ handler routine (due to jsrfar race condition).
@@ -620,6 +640,10 @@ const ubyte EXTAPI_scnsiz = $0D
const ubyte EXTAPI_kbd_leds = $0E
const ubyte EXTAPI_memory_decompress_from_func = $0F
const ubyte EXTAPI_default_palette = $10
const ubyte EXTAPI_has_machine_property = $11
const ubyte EXTAPI_kbdbuf_get = $12
const ubyte EXTAPI_kbdbuf_clear = $13
const ubyte EXTAPI_blink_enable = $14
; extapi16 call numbers
const ubyte EXTAPI16_test = $00
@@ -627,6 +651,10 @@ const ubyte EXTAPI16_stack_push = $01
const ubyte EXTAPI16_stack_pop = $02
const ubyte EXTAPI16_stack_enter_kernal_stack = $03
const ubyte EXTAPI16_stack_leave_kernal_stack = $04
const ubyte EXTAPI16_xmacptr = $05
const ubyte EXTAPI16_xmciout = $06
const ubyte EXTAPI16_hbload = $07
const ubyte EXTAPI16_get_last_far_bank = $08
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) {
@@ -676,6 +704,30 @@ sub mouse_present() -> bool {
return cx16.r0L != $fc ; $fc = BAT_FAIL
}
asmsub get_charset() -> ubyte @A {
;Get charset mode. result: 0=unknown, 1=ISO, 2=PETSCII upper case/gfx, 3=PETSCII lowercase.
%asm {{
KERNAL_MODE = $0372 ; internal kernal variable, risky to read it, but it's ben stable for many releases.
lda KERNAL_MODE
beq _end
bit #$40 ;ISO mode flag
beq + ;usually KERNAL_MODE 1 or 6 (| $40)
lda #1
bra _end
+ bit #1 ;PETSCII upper case/graphics
bne + ;usually KERNAL_MODE 2 or 4
lda #2
bra _end
+ lda #3 ;PETSCII upper/lower case
_end:
rts
}}
}
; shims for the kernal routines called via the extapi call:
asmsub mouse_set_pos(uword xpos @R0, uword ypos @R1) clobbers(X) {
@@ -758,31 +810,42 @@ asmsub set_default_palette() {
}}
}
asmsub get_charset() -> ubyte @A {
;Get charset mode. result: 0=unknown, 1=ISO, 2=PETSCII upper case/gfx, 3=PETSCII lowercase.
asmsub has_machine_property(ubyte property @X) clobbers(A,X) -> bool @Pc {
; requires rom v49+
%asm {{
lda #EXTAPI_has_machine_property
jmp cx16.extapi
}}
}
KERNAL_MODE = $0372 ; internal kernal variable, risky to read it, but it's ben stable for many releases.
lda KERNAL_MODE
beq _end
asmsub kbdbuf_get() clobbers(X,Y) -> ubyte @A {
; requires rom v49+
%asm {{
lda #EXTAPI_kbdbuf_get
jmp cx16.extapi
}}
}
bit #$40 ;ISO mode flag
beq + ;usually KERNAL_MODE 1 or 6 (| $40)
lda #1
bra _end
asmsub kbdbuf_clear() clobbers(A) {
; requires rom v49+
%asm {{
lda #EXTAPI_kbdbuf_clear
jmp cx16.extapi
}}
}
+ bit #1 ;PETSCII upper case/graphics
bne + ;usually KERNAL_MODE 2 or 4
lda #2
bra _end
+ lda #3 ;PETSCII upper/lower case
_end:
rts
asmsub blink_enable(bool enable @X) clobbers(A,X) {
; requires rom v49+
%asm {{
txa
and #1
eor #1
tax
lda #EXTAPI_blink_enable
jmp cx16.extapi
}}
}
; TODO : implement shims for the remaining extapi calls.
; ---- end of kernal routines ----
@@ -1191,7 +1254,7 @@ asmsub restore_vera_context() clobbers(A) {
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
; See https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
; See https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
%asm {{
sei
sta P8ZP_SCRATCH_REG
@@ -1273,7 +1336,7 @@ inline asmsub disable_irqs() clobbers(A) {
asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
; Install the "master IRQ handler" that will dispatch IRQs
; to the registered handler for each type. (Only Vera IRQs supported for now).
; to the registered handler for each type.
; The handlers don't need to clear its ISR bit, but have to return 0 or 1 in A,
; where 1 means: continue with the system IRQ handler, 0 means: don't call that.
%asm {{
@@ -1281,7 +1344,11 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
sei
bcc +
lda #%00001111
trb cx16.VERA_IEN ; disable all IRQ sources
; disable all IRQ sources on VERA and VIA1
trb cx16.VERA_IEN
lda #%01111111
sta cx16.via1ier
+ lda #<_irq_dispatcher
ldy #>_irq_dispatcher
sta cbm.CINV
@@ -1299,6 +1366,8 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
sty _aflow_vec+1
sta _sprcol_vec
sty _sprcol_vec+1
sta _timer1_vec
sty _timer1_vec+1
plp
rts
@@ -1308,11 +1377,12 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_timer1_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
; order of handling: LINE, SPRCOL, AFLOW, VSYNC.
; order of handling: LINE, TIMER1(VIA1), VSYNC, SPRCOL, AFLOW
jsr sys.save_prog8_internals
cld
lda cx16.VERA_ISR
@@ -1328,7 +1398,17 @@ _irq_dispatcher
tsb _continue_with_system_handler
pla
+ lsr a
; timer1 (VIA1) irq?
+ pha
lda #%01000000
and cx16.via1ifr
beq + ; not timer1 irq
sta cx16.via1ifr
jsr _timer1_handler
tsb _continue_with_system_handler
+ pla
lsr a
bcc +
pha
jsr _vsync_handler
@@ -1373,6 +1453,8 @@ _sprcol_handler
jmp (_sprcol_vec)
_aflow_handler
jmp (_aflow_vec)
_timer1_handler
jmp (_timer1_vec)
}}
}
@@ -1437,6 +1519,28 @@ asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) {
}}
}
asmsub set_timer1_irq_handler(uword address @AY) {
; Sets the VIA1 TIMER1 irq handler to use with enable_irq_handlers(). Does not enable or disable VIA1 timer irqs setting.
%asm {{
php
sei
sta enable_irq_handlers._timer1_vec
sty enable_irq_handlers._timer1_vec+1
plp
rts
}}
}
sub set_timer1(uword delay, bool keeprunning) {
; -- set VIA1 timer1 to trigger after the given delay (cycles). Enables VIA timer1 irqs if delay>0, otherwise disables it.
cx16.via1acr &= %00111111
if keeprunning
cx16.via1acr |= %01000000 ; continuous (free-run) mode
cx16.via1ier = if delay==0 then %01000000 else %11000000
cx16.via1t1lw = delay
}
inline asmsub disable_irq_handlers() {
; back to the system default IRQ handler.
@@ -1479,7 +1583,7 @@ sub search_x16edit() -> ubyte {
sub set_program_args(str args_ptr, ubyte args_size) {
; -- Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
; see https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
sys.push(getrambank())
rambank(0)
sys.memcopy(args_ptr, $bf00, args_size)
@@ -1491,7 +1595,7 @@ sub search_x16edit() -> ubyte {
asmsub get_program_args(^^ubyte buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
; see https://github.com/X16Community/x16-docs/blob/r49/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
%asm {{
lda #0
rol a
@@ -1555,6 +1659,8 @@ _continue iny
}
}
%import shared_sys_functions
sys {
; ------- lowlevel system routines --------
@@ -1632,6 +1738,16 @@ asmsub restore_irq() clobbers(A) {
and #%11110000 ; disable all Vera IRQs but the vsync
ora #%00000001
sta cx16.VERA_IEN
lda #%01111111
sta cx16.via1ier
; set timer1 back to free running mode
lda cx16.via1acr
and %00111111
ora %01000000
sta cx16.via1acr
lda #$ff
sta cx16.via1t1ll
sta cx16.via1t1lh
plp
rts
.section BSS_NOCLEAR
@@ -1807,182 +1923,6 @@ _larger
}
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.
%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!
; If you have to copy overlapping memory regions, consider using
; the cx16 specific kernal routine `memory_copy` (make sure kernal rom is banked in).
; note: can't be inlined because is called from asm as well.
; also: doesn't use cx16 ROM routine so this always works even when ROM is not banked in.
%asm {{
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
dey
beq +
- lda (cx16.r0),y
sta (cx16.r1),y
dey
bne -
+ lda (cx16.r0),y
sta (cx16.r1),y
rts
_longcopy
pha ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (cx16.r0),y
sta (cx16.r1),y
iny
bne -
inc cx16.r0+1
inc cx16.r1+1
dex
bne -
ply
bne _copyshort
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_W1
sty P8ZP_SCRATCH_W1+1
ldx P8ZP_SCRATCH_W1+1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (cx16.r0),y
cmp (cx16.r1),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc cx16.r0+1
inc cx16.r1+1
dec P8ZP_SCRATCH_W1+1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_W1
bne +
rts
+ ldy #0
- lda (cx16.r0),y
cmp (cx16.r1),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_W1
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 {{
lda #8
@@ -1997,49 +1937,6 @@ _no_msb_size
}}
}
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 exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
@@ -2077,20 +1974,6 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
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
@@ -2141,7 +2024,7 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub pushl(long value @R0R1_32) {
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
@@ -2154,7 +2037,7 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub popl() -> long @R0R1_32 {
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
@@ -2224,6 +2107,7 @@ asmsub init_system() {
sta $00 ; select ram bank
lda #0
sta $01 ; set ROM bank to kernal bank to speed up kernal calls
sta cx16.VERA_DC_BORDER
tax
tay
cli
+1 -1
View File
@@ -137,7 +137,7 @@ verafx {
}
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1_32 {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1 {
; Returns the 32 bits signed result in R0:R1 (lower word, upper word).
; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
+47 -3
View File
@@ -524,7 +524,7 @@ log2_tab
sbc P8ZP_SCRATCH_REG
bcs +
eor #255
inc a
adc #1
+ rts
}}
}
@@ -543,7 +543,7 @@ log2_tab
sta cx16.r0H
lda cx16.r0L
eor #255
inc a
adc #1
sta cx16.r0L
bne +
inc cx16.r0H
@@ -674,7 +674,7 @@ log2_tab
return crc32_end_result()
}
asmsub crc32_end_result() -> long @R14R15_32 {
asmsub crc32_end_result() -> long @R14R15 {
; finalize the "streaming" crc32
; returns the result value in cx16.r15 (high word) and r14 (low word)
%asm {{
@@ -731,4 +731,48 @@ log2_tab
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
sub gcd(uword @nozp a, uword @nozp b) -> uword {
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm). Clobbers R0,R1 !
alias aa = cx16.r0
alias bb = cx16.r1
aa=a
bb=b
; Handle the case where one number is zero
if aa == 0 return bb
if bb == 0 return aa
; Count the number of common factors of 2
ubyte shift = 0
; Count common factors of 2 by shifting both numbers right until at least one is odd
while ((aa | bb) & 1) == 0 {
aa >>= 1
bb >>= 1
shift++
}
; Remove remaining factors of 2 from a
while (aa & 1) == 0
aa >>= 1
; Now a is odd. Loop until b becomes zero
while bb != 0 {
; Remove remaining factors of 2 from b
while (bb & 1) == 0
bb >>= 1
; Make sure a <= b
if aa > bb
swap(aa, bb)
bb -= aa
}
; Return the result multiplied by the common factors of 2
return aa << shift
}
}
+481
View File
@@ -0,0 +1,481 @@
%import strings
%import syslib
diskio {
%option no_symbol_prefixing, ignore_unused
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
const ubyte STATUS_EOF=$40
ubyte @shared drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
str list_filename = "?" * 50
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
void cbm.CHKIN(READ_IO_CHANNEL)
repeat 4 {
void cbm.CHRIN() ; skip the 4 prologue bytes
}
; while not stop key pressed / EOF encountered, read data.
status = cbm.READST()
if status!=0 {
status = 1
goto io_error
}
while status==0 {
ubyte low = cbm.CHRIN()
ubyte high = cbm.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp character
repeat {
character = cbm.CHRIN()
if character==0
break
txt.chrout(character)
}
txt.nl()
void cbm.CHRIN() ; skip 2 bytes
void cbm.CHRIN()
status = cbm.READST()
void cbm.STOP()
if_z
break
}
status = cbm.READST()
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
if status!=0 and status & STATUS_EOF == 0 {
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.nl()
return false
}
return true
}
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.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
; 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.
ubyte files_found = 0
uword buf_ptr = filenames_buffer
@(buf_ptr) = 0
cbm.SETNAM(1, "$")
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
void cbm.CHKIN(READ_IO_CHANNEL)
repeat 4 {
void cbm.CHRIN() ; skip the 4 prologue bytes
}
; while not stop key pressed / EOF encountered, read data.
status = cbm.READST()
if status!=0 {
status = 1
goto io_error
}
repeat 4 void cbm.CHRIN() ; skip the 4 prologue bytes
while cbm.CHRIN()!=0 {
; skip the disk name
}
while status==0 {
while cbm.CHRIN()!='"' {
if cbm.READST()!=0
goto end_listing
; read up to first quote
}
^^ubyte name_ptr = list_filename
; read and store file name
repeat {
cx16.r0L = cbm.CHRIN()
if cx16.r0L=='"'
break
if cx16.r0L==0
cx16.r0L = '?'
@(name_ptr) = cx16.r0L
name_ptr++
}
@(name_ptr) = 0
if pattern_ptr==0 or strings.pattern_match(list_filename, pattern_ptr) {
buf_ptr += strings.copy(list_filename, buf_ptr) + 1
files_found++
}
while cbm.CHRIN()!=0 {
; read up to first quote
}
void cbm.CHRIN()
void cbm.CHRIN()
status = cbm.READST()
void cbm.STOP()
if_z
break
}
end_listing:
@(buf_ptr) = 0
status = cbm.READST()
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
return files_found
}
sub diskname() -> str {
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
bool okay = false
void cbm.OPEN() ; open 12,8,0,"$"
void cbm.CHKIN(READ_IO_CHANNEL)
void cbm.CHRIN()
if cbm.READST()!=0
goto io_error
while cbm.CHRIN()!='"' {
; skip up to entry name
}
cx16.r0 = &list_filename
repeat {
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
@(cx16.r0) = 0
cx16.r0--
}
break
}
cx16.r0++
}
okay = true
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
if okay
return &list_filename
return 0
}
; Use kernal LOAD routine to load the given program file in memory.
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the end load address+1 if successful or 0 if a load error occurred.
sub load(str filenameptr, uword address_override) -> uword {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN()
void cbm.CHKIN(READ_IO_CHANNEL)
if address_override==0 {
; fetch load address from prg header first 2 bytes
%asm {{
jsr cbm.CHRIN
sta P8ZP_SCRATCH_PTR
jsr cbm.CHRIN
sta P8ZP_SCRATCH_PTR+1
}}
} else {
; skip the load address from the prg header
%asm {{
jsr cbm.CHRIN
jsr cbm.CHRIN
lda address_override
ldy address_override+1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
}}
}
if cbm.READST()!=0 {
address_override = 0
goto end
}
readloop:
%asm {{
jsr cbm.READST
cmp #STATUS_EOF
beq _done
cmp #0
bne _error
jsr cbm.CHRIN
ldy #0
sta (P8ZP_SCRATCH_PTR),y
inc P8ZP_SCRATCH_PTR
bne readloop
inc P8ZP_SCRATCH_PTR+1
bne readloop
_error:
lda #0
beq _done2
_done:
lda P8ZP_SCRATCH_PTR
ldy P8ZP_SCRATCH_PTR+1
_done2:
sta address_override
sty address_override+1
}}
end:
cbm.CLOSE(READ_IO_CHANNEL)
cbm.CLRCHN() ; restore default i/o devices
return address_override
}
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(str filenameptr, uword start_address) -> uword {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN()
void cbm.CHKIN(READ_IO_CHANNEL)
%asm {{
lda start_address
ldy start_address+1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
}}
goto diskio.load.readloop
}
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(str filenameptr, uword startaddress, uword savesize) -> bool {
bool skip_header = false
internal_save:
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN()
cbm.CHKOUT(WRITE_IO_CHANNEL)
bool status=false
if cbm.READST()==0 {
%asm {{
; write 2 bytes prg header
lda skip_header
bne +
lda startaddress
jsr cbm.CHROUT
lda startaddress+1
jsr cbm.CHROUT
+ lda startaddress
ldy startaddress+1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
jsr cbm.READST
bne _error
; decrease savesize by 1 for the loop
lda savesize
bne +
dec savesize+1
+ dec savesize
ldy #0
_loop:
lda (P8ZP_SCRATCH_PTR),y
jsr cbm.CHROUT
+ lda savesize
bne +
lda savesize+1
beq _done
dec savesize+1
+ dec savesize
iny
bne _loop
jsr cbm.READST
bne _error
inc P8ZP_SCRATCH_PTR+1
jmp _loop
_done:
inc status
_error:
}}
}
cbm.CLOSE(WRITE_IO_CHANNEL)
cbm.CLRCHN() ; restore default i/o devices
return status
}
; like save() but omits the 2 byte prg header.
sub save_raw(str filenameptr, uword startaddress, uword savesize) -> bool {
diskio.save.filenameptr = filenameptr
diskio.save.startaddress = startaddress
diskio.save.savesize = savesize
diskio.save.skip_header = true
goto diskio.save.internal_save
}
sub status() -> str {
; -- retrieve the disk drive's current status message
^^ubyte messageptr = &list_filename
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
void cbm.CHKIN(15) ; use #15 as input channel
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
@(messageptr) = cx16.r5L
messageptr++
}
@(messageptr) = 0
done:
cbm.CLOSE(15)
cbm.CLRCHN() ; restore default i/o devices
return list_filename
}
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
; in case of IO error, returns 255 (CMDR-DOS itself is physically unable to return such a value)
sub status_code() -> ubyte {
if cbm.READST()==128 {
return 255
}
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
if_cs
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
list_filename[0] = cbm.CHRIN()
list_filename[1] = cbm.CHRIN()
list_filename[2] = 0
while cbm.READST()==0 {
void cbm.CHRIN()
}
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(15)
return conv.str2ubyte(list_filename)
io_error:
cbm.CLRCHN()
cbm.CLOSE(15)
return 255
}
sub get_loadaddress(str filename) -> uword {
; get the load adress from a PRG file (usually $0801 but it can be different)
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"filename"
void cbm.CHKIN(READ_IO_CHANNEL)
cx16.r0L = cbm.CHRIN()
cx16.r0H = cbm.CHRIN()
if cbm.READST()!=0
cx16.r0 = 0
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
return cx16.r0
}
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: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"filename"
void cbm.CHKIN(READ_IO_CHANNEL)
void cbm.CHRIN()
cx16.r0bL = cbm.READST()==0
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
return cx16.r0bL
}
sub delete(str filenameptr) {
; -- delete a file on the drive
list_filename[0] = 's'
list_filename[1] = ':'
ubyte flen = strings.copy(filenameptr, &list_filename+2)
cbm.SETNAM(flen+2, list_filename)
cbm.SETLFS(1, drivenumber, 15)
void cbm.OPEN()
cbm.CLRCHN()
cbm.CLOSE(1)
}
sub rename(str oldfileptr, str newfileptr) {
; -- rename a file on the drive
list_filename[0] = 'r'
list_filename[1] = ':'
ubyte flen_new = strings.copy(newfileptr, &list_filename+2)
list_filename[flen_new+2] = '='
ubyte flen_old = strings.copy(oldfileptr, &list_filename+3+flen_new)
cbm.SETNAM(3+flen_new+flen_old, list_filename)
cbm.SETLFS(1, drivenumber, 15)
void cbm.OPEN()
cbm.CLRCHN()
cbm.CLOSE(1)
}
sub send_command(str commandptr) {
; -- send a dos command to the drive
cbm.SETNAM(strings.length(commandptr), commandptr)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLRCHN()
cbm.CLOSE(15)
}
}
+166
View File
@@ -0,0 +1,166 @@
%option enable_floats, no_symbol_prefixing, ignore_unused
%import shared_floats_functions
floats {
; ---- this block contains C-64 floating point related functions ----
; ---- C64 basic and kernal ROM float constants and functions ----
; 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 = $5e
; 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.
;extsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
;extsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
;;
;
;extsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
;extsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
;
;
;extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
;extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
;
;extsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
;extsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
; PET32 BASIC 4.0 ADDRESSES FOUND:
; (source: https://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt )
;; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
;; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
extsub $c92d = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
extsub $c4bc = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
extsub $c2ea = AYINT() clobbers(A,X,Y) ; fac1-> signed word in ????? MSB FIRST. (might throw ILLEGAL QUANTITY) DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD.
extsub $cbc2 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
extsub $cd32 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
extsub $cd0a = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
extsub $ccd8 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
extsub $cd42 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
extsub $cd45 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
extsub $cb20 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
extsub $cb61 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
extsub $cb5e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
extsub $cc48 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
extsub $cc45 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
extsub $cc18 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
extsub $cc34 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
extsub $c9a0 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
extsub $c99d = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
extsub $c97f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
extsub $c989 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
extsub $c986 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
extsub $cd91 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
extsub $d112 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
extsub $ca0d = NORMAL() clobbers(A) ; normalize FAC1
extsub $cd61 = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
extsub $cd6f = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
extsub $cd8e = ABS() ; fac1 = ABS(fac1)
extsub $cdd1 = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in FAC, with the MSB FIRST.
extsub $ce02 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
extsub $cf93 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
extsub $d108 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
extsub $d14b = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
extsub $d184 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
extsub $d229 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
extsub $d282 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
extsub $d289 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
extsub $d2d2 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
extsub $d32c = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub AYINT2() clobbers(X) -> word @AY {
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
; (might throw ILLEGAL QUANTITY)
%asm {{
jsr AYINT
ldx #<floats_temp_var
ldy #>floats_temp_var
jsr MOVMF
lda floats_temp_var+4
ldy floats_temp_var+3
rts
}}
}
asmsub FREADUY(ubyte value @ Y) clobbers(A,X,Y) {
; -- 8 bit unsigned Y -> float in fac1
%asm {{
lda #0
jmp GIVAYF
}}
}
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
; ---- 8 bit signed A -> float in fac1
%asm {{
tay
bpl +
lda #$ff
jmp GIVAYF
+ lda #0
jmp GIVAYF
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{
jsr GETADR ; this uses the inverse order, Y/A
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
; 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!
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+79
View File
@@ -0,0 +1,79 @@
; Play sounds from your PET speaker
; Uses the VIA timer to be able to play them without blocking the program in a delay loop.
; Notes lower than A3 cannot be played with this mechanism and need a custom CPU delay loop.
;
; inspiration: http://blog.tynemouthsoftware.co.uk/2022/05/pet-sounds.html
;
; Example:
; petsnd.on()
; petsnd.note(petsnd.C_4)
; sys.wait(30)
; petsnd.note(petsnd.E_4)
; sys.wait(30)
; petsnd.note(petsnd.A_4)
; sys.wait(30)
; petsnd.off()
petsnd {
%option ignore_unused
; octaves:
const ubyte OCTAVES_456 = 1
const ubyte OCTAVES_567 = 2
const ubyte OCTAVES_678 = 3
; notes:
const ubyte A_SHARP_3 = $FF ; A#3 = 233.08 Hz
const ubyte B_3 = $FA ; B3 = 246.94 Hz
const ubyte C_4 = $EE ; C4 = 261.63 Hz
const ubyte C_SHARP_4 = $E0 ; C#4 = 277.18 Hz
const ubyte D_4 = $D2 ; D4 = 293.66 Hz
const ubyte D_SHARP_4 = $C7 ; D#4 = 311.13 Hz
const ubyte E_4 = $BC ; E4 = 329.63 Hz
const ubyte F_4 = $B1 ; F4 = 349.23 Hz
const ubyte F_SHARP_4 = $A8 ; F#4 = 369.99 Hz
const ubyte G_4 = $9E ; G4 = 392.00 Hz
const ubyte G_SHARP_4 = $95 ; G#4 = 415.30 Hz
const ubyte A_4 = $8C ; A4 = 440.00 Hz
const ubyte A_SHARP_4 = $85 ; A#4 = 466.16 Hz
const ubyte B_4 = $7D ; B4 = 493.78 Hz
const ubyte C_5 = $76 ; C5 = 523.25 Hz
const ubyte C_SHARP_5 = $6E ; C#5 = 554.37 Hz
const ubyte D_5 = $68 ; D5 = 587.33 Hz
const ubyte D_SHARP_5 = $63 ; D#5 = 622.25 Hz
const ubyte E_5 = $5D ; E5 = 659.26 Hz
const ubyte F_5 = $58 ; F5 = 698.46 Hz
const ubyte F_SHARP_5 = $53 ; F#5 = 739.99 Hz
const ubyte G_5 = $4E ; G5 = 783.99 Hz
const ubyte G_SHARP_5 = $4A ; G#5 = 830.61 Hz
const ubyte A_5 = $45 ; A5 = 880.00 Hz
const ubyte A_SHARP_5 = $41 ; A#5 = 932.33 Hz
const ubyte B_5 = $3D ; B5 = 987.77 Hz
const ubyte C_6 = $39 ; C6 = 1046.50 Hz
sub on() {
pet.via1acr = $10 ; "shift out free running at T2 rate"
octaves(petsnd.OCTAVES_456)
}
sub off() {
pet.via1t2 = 0 ; clear note
pet.via1acr = 0 ; stop sound.
}
sub octaves(ubyte oct) {
; set octave(s), choice from range 1 to 3. (1=octaves 4,5,6, 2=octaves 5,6,7, 3=octaves 6,7,8)
; the VIA shift register value determines octave
when oct {
OCTAVES_456 -> pet.via1sr = %00001111
OCTAVES_567 -> pet.via1sr = %00110011
OCTAVES_678 -> pet.via1sr = %01010101
else -> pet.via1sr = 0
}
}
sub note(ubyte n) {
pet.via1t2 = n ; timer 2 rate (L)
}
}
+101 -252
View File
@@ -11,6 +11,13 @@ cbm {
&ubyte TIME_MID = $8e ; .. mid byte
&ubyte TIME_LO = $8f ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $96 ; kernal status variable for I/O
&ubyte FNLEN = $D1 ; Length of filename
&ubyte LFN = $D2 ; Current Logical File Number
&ubyte SECADR = $D3 ; Secondary address
&ubyte DEVNUM = $D4 ; Device number
&ubyte CURS_X = $C6 ; Cursor column
&ubyte CURS_Y = $D8 ; Cursor row
&ubyte FNADR = $DA ; Pointer to file name
&uword CINV = $0090 ; IRQ vector (in ram)
&uword CBINV = $0092 ; BRK vector (in ram)
@@ -28,13 +35,44 @@ extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; define an
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an output channel
extsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
extsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
extsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
extsub $FFE7 = CLALL() clobbers(A,X) ; close all files
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $F563 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; open a logical file
extsub $F2E2 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; close a logical file
asmsub READST() -> ubyte @ A {
; read io status
%asm {{
lda STATUS
rts
}}
}
asmsub SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) {
; set logical file parameters
%asm {{
sta LFN ; LFN
stx DEVNUM ; Device address
sty SECADR ; Secondary address
rts
}}
}
asmsub SETNAM(ubyte namelen @ A, str filename @ XY) {
; set filename parameters
%asm {{
sta FNLEN
stx FNADR
sty FNADR+1
rts
}}
}
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
; -- just like STOP, but omits the special keys result value in A.
; just for convenience because most of the times you're only interested in the stop pressed or not status.
@@ -81,7 +119,7 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
asmsub SETTIML(long jiffies @R0R1) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
@@ -91,7 +129,7 @@ asmsub SETTIML(long jiffies @R0R1_32) {
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
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
@@ -116,6 +154,54 @@ asmsub kbdbuf_clear() {
}
pet {
const uword VIA1_BASE = $e840 ;VIA 6522
&ubyte via1prb = VIA1_BASE + 0
&ubyte via1pra = VIA1_BASE + 1
&ubyte via1ddrb = VIA1_BASE + 2
&ubyte via1ddra = VIA1_BASE + 3
&uword via1t1 = VIA1_BASE + 4
&ubyte via1t1l = VIA1_BASE + 4
&ubyte via1t1h = VIA1_BASE + 5
&uword via1t1lw = VIA1_BASE + 6
&ubyte via1t1ll = VIA1_BASE + 6
&ubyte via1t1lh = VIA1_BASE + 7
&uword via1t2 = VIA1_BASE + 8
&ubyte via1t2l = VIA1_BASE + 8
&ubyte via1t2h = VIA1_BASE + 9
&ubyte via1sr = VIA1_BASE + 10
&ubyte via1acr = VIA1_BASE + 11
&ubyte via1pcr = VIA1_BASE + 12
&ubyte via1ifr = VIA1_BASE + 13
&ubyte via1ier = VIA1_BASE + 14
&ubyte via1ora = VIA1_BASE + 15
; these are not general kernal routines they're the full BASIC comands
extsub $ff93 = concat() clobbers (A,X,Y)
extsub $ff96 = dopen() clobbers (A,X,Y)
extsub $ff99 = dclose() clobbers (A,X,Y)
extsub $ff9c = record() clobbers (A,X,Y)
extsub $ff9f = header() clobbers (A,X,Y)
extsub $ffa2 = collect() clobbers (A,X,Y)
extsub $ffa5 = backup() clobbers (A,X,Y)
extsub $ffa8 = copy() clobbers (A,X,Y)
extsub $ffab = append() clobbers (A,X,Y)
extsub $ffae = dsave() clobbers (A,X,Y)
extsub $ffb1 = dload() clobbers (A,X,Y)
extsub $ffb4 = catalog() clobbers (A,X,Y)
extsub $ffb7 = rename() clobbers (A,X,Y)
extsub $ffba = scratch() clobbers (A,X,Y)
extsub $ffc0 = open() clobbers (A,X,Y)
extsub $ffc3 = close() clobbers (A,X,Y)
extsub $ffd5 = load() clobbers (A,X,Y)
extsub $ffd8 = save() clobbers (A,X,Y)
extsub $ffdb = verify() clobbers (A,X,Y)
extsub $ffde = sys() clobbers (A,X,Y)
}
%import shared_sys_functions
sys {
; ------- lowlevel system routines --------
@@ -186,196 +272,6 @@ _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.
%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 -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
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
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
}}
}
sub disable_caseswitch() {
; PET doesn't have a key to swap case, so no-op
}
@@ -384,49 +280,6 @@ _no_msb_size
; PET doesn't have a key to swap case, so no-op
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
.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 exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
@@ -464,20 +317,6 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
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
@@ -532,7 +371,7 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub pushl(long value @R0R1_32) {
inline asmsub pushl(long value @R0R1) {
%asm {{
lda cx16.r0
pha
@@ -545,7 +384,7 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub popl() -> long @R0R1_32 {
inline asmsub popl() -> long @R0R1 {
%asm {{
pla
sta cx16.r0+3
@@ -605,6 +444,16 @@ cx16 {
&word r14s = $7ffc
&word r15s = $7ffe
; signed long versions
&long r0r1sl = $7fe0
&long r2r3sl = $7fe4
&long r4r5sl = $7fe8
&long r6r7sl = $7fec
&long r8r9sl = $7fe0
&long r10r11sl = $7fe4
&long r12r13sl = $7fe8
&long r14r15sl = $7fec
; ubyte versions (low and high bytes)
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
+68 -34
View File
@@ -60,6 +60,45 @@ sub uppercase() {
txt.chrout(142)
}
asmsub column(ubyte col @A) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sta $c6
rts
}}
}
asmsub get_column() -> ubyte @A {
%asm {{
lda $c6
rts
}}
}
asmsub row(ubyte rownum @A) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
ldy $c6
tax
jmp plot
}}
}
asmsub get_row() -> ubyte @A {
%asm {{
lda $d8
rts
}}
}
asmsub get_cursor() -> ubyte @X, ubyte @Y {
%asm {{
ldx $c6
ldy $d8
rts
}}
}
asmsub scroll_left () clobbers(A, X, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
@@ -137,21 +176,21 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
pha
tya
asl a
tay
bit $d5
bvc +
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
_screenrows .word cbm.Screen + range(0, 1000, 40)
_screenrows .word cbm.Screen + range(0, 2000, 40)
; !notreached!
}}
}
@@ -162,16 +201,16 @@ asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
pha
tya
asl a
tay
bit $d5
bvc +
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
}}
@@ -181,25 +220,17 @@ sub setclr (ubyte col, ubyte row, ubyte color) {
; --- dummy function on PET
}
inline asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- dummy function on PET
%asm {{
lda #1
}}
}
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor_ignored) {
; ---- set char at the given position on the screen. charcolor is ignored on PET
inline asmsub setcc (ubyte col @X, ubyte row @Y, ubyte character @A, ubyte charcolor @R0) {
; ---- set char at the given position on the screen. NOTE: charcolor is ignored on PET so identical to setchr
%asm {{
lda row
asl a
tay
lda setchr._screenrows+1,y
sta P8ZP_SCRATCH_W1+1
lda setchr._screenrows,y
clc
adc col
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda character
ldy #0
sta (P8ZP_SCRATCH_W1),y
rts
jsr txt.setchr
}}
}
@@ -208,7 +239,10 @@ asmsub plot (ubyte col @ Y, ubyte row @ X) {
stx $d8
txa
asl a
tax
bit $d5
bvc +
asl a
+ tax
lda setchr._screenrows,x
sta $c4
lda setchr._screenrows+1,x
+65
View File
@@ -0,0 +1,65 @@
%import textio
petgfx {
%option ignore_unused
; Plot at "petscii subpixel" x,y , which is twice the text screen resolution so 80 * 50 , instead of 40*25.
; NOTE: assumes the screen is cleared with spaces beforehand.
sub hline(ubyte x, ubyte y, ubyte length) {
; horizontal line
repeat length {
plot(x, y)
x++
}
}
sub vline(ubyte x, ubyte y, ubyte length) {
; vertical line
repeat length {
plot(x, y)
y++
}
}
sub plot(ubyte x, ubyte y) {
ubyte subchar
x >>= 1
rol(subchar)
y >>= 1
if_cc
subchar++
else
subchar = (subchar<<2) + 4
ubyte existing = cbm.Screen[y*(txt.DEFAULT_WIDTH as uword) + x]
; search the index of the current subpixels in the cell
%asm {{
ldy #0
lda p8v_existing
- cmp p8v_subpixels,y
beq +
iny
cpy #16
bne -
+ sty p8v_existing
}}
; for cx16.r0H in 0 to 15 {
; if subpixels[cx16.r0H] == existing {
; existing = cx16.r0H
; break
; }
; }
; update screen cell with new subpixel
txt.setchr(x, y, subpixels[subchar | existing])
; 0 1 2 3 4 5 6 7 8 9 A B C D E F
; -- #- -# ## -- #- -# ## -- #- -# ## -- #- -# ##
; -- -- -- -- #- #- #- #- -# -# -# -# ## ## ## ##
; 32, 126, 124, 226, 123, 97, 255, 236, 108, 127, 225, 251, 98, 252, 254, 160
ubyte[16] subpixels = [32, 126, 124, 226, 123, 97, 255, 236, 108, 127, 225, 251, 98, 252, 254, 160]
}
}
+164 -28
View File
@@ -323,34 +323,6 @@ _c1_zero lda (P8ZP_SCRATCH_W2),y
+ rts
.pend
strncmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Compares up to maximum length specified in X.
; Returns -1,0,1 in A, depending on the ordering. Clobbers X & Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_loop lda (P8ZP_SCRATCH_W1),y
beq _c1_zero
cmp (P8ZP_SCRATCH_W2),y
beq _equal
bmi _less
lda #1
rts
_less lda #-1
rts
_equal dex
bne +
lda #0
rts
+ iny
bne _loop
_c1_zero lda (P8ZP_SCRATCH_W2),y
beq +
lda #-1
+ rts
.pend
strlen .proc
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
@@ -605,6 +577,56 @@ long_xor_inplace .proc
rts
.pend
long_negate_inplace .proc
; negate a signed long pointed to by AY in place
; Uses two's complement: flip all bits then add 1
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
ldy #$00
lda (P8ZP_SCRATCH_PTR),y
eor #$FF
sta (P8ZP_SCRATCH_PTR),y
iny
lda (P8ZP_SCRATCH_PTR),y
eor #$FF
sta (P8ZP_SCRATCH_PTR),y
iny
lda (P8ZP_SCRATCH_PTR),y
eor #$FF
sta (P8ZP_SCRATCH_PTR),y
iny
lda (P8ZP_SCRATCH_PTR),y
eor #$FF
sta (P8ZP_SCRATCH_PTR),y
; Add 1 to get two's complement (incrementing the 32-bit value)
clc
lda #$01
ldy #$00
adc (P8ZP_SCRATCH_PTR),y
sta (P8ZP_SCRATCH_PTR),y
bcc +
iny
lda #$00
adc (P8ZP_SCRATCH_PTR),y
sta (P8ZP_SCRATCH_PTR),y
bcc +
iny
lda #$00
adc (P8ZP_SCRATCH_PTR),y
sta (P8ZP_SCRATCH_PTR),y
bcc +
iny
lda #$00
adc (P8ZP_SCRATCH_PTR),y
sta (P8ZP_SCRATCH_PTR),y
+ rts
.pend
long_shiftleftX_inplace .proc
; bit shift left X bits the long value pointed to by AY
cpx #0
@@ -660,3 +682,117 @@ long_shiftrightX_inplace .proc
bne -
_end rts
.pend
compare_long_gt_0 .proc
; pointer to 32 bits signed long in P8ZP_SCRATCH_W1, carry Set: value>0, carry clear: value <=0
ldy #3
lda (P8ZP_SCRATCH_W1),y
bmi _fail
dey
ora (P8ZP_SCRATCH_W1),y
dey
ora (P8ZP_SCRATCH_W1),y
dey
ora (P8ZP_SCRATCH_W1),y
beq _fail
sec
rts
_fail clc
rts
.pend
compare_long_le_0 .proc
; pointer to 32 bits signed long in P8ZP_SCRATCH_W1, carry Set: value<=0, carry clear: value >0
ldy #3
lda (P8ZP_SCRATCH_W1),y
bmi _success
dey
ora (P8ZP_SCRATCH_W1),y
dey
ora (P8ZP_SCRATCH_W1),y
dey
ora (P8ZP_SCRATCH_W1),y
beq _success
clc
rts
_success sec
rts
.pend
copyfrompointer1topointer2_word .proc
; copy word pointed to by P8ZP_SCRATCH_W1 to word pointed to by AY
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
copyfrompointer1topointer2_long .proc
; copy long pointed to by P8ZP_SCRATCH_W1 to long pointed to by AY
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
swap_words .proc
; -- swap word values pointed to by P8ZP_SCRATCH_W1 and AY
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tax
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
iny
txa
sta (P8ZP_SCRATCH_W2),y
rts
.pend
swap_longs .proc
; -- swap long values pointed to by P8ZP_SCRATCH_W1 and AY. Clobbers R14 and R15 (temporary swap var)
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #3
- lda (P8ZP_SCRATCH_W1),y
sta cx16.r14r15sl,y
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
dey
bpl -
ldy #3
- lda cx16.r14r15sl,y
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
@@ -222,6 +222,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, "$:*=c") ; on C64 (1581 diskdrive) the type for directories is CBM
diskio.lf_start_list.pattern_ptr = pattern_ptr
goto diskio.lf_start_list.start_list_internal
}
@@ -230,6 +231,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
}
@@ -719,6 +734,7 @@ io_error:
if cbm.READST()!=0
cx16.r0 = 0
}
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
return cx16.r0
}
@@ -229,7 +229,51 @@ _offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
bne -
+ rts
}}
}
}
sub iso2petscii(ubyte iso_char) -> ubyte {
; --converts iso 8859-15 character to petscii character (lowercase)
if iso_char & $7f <= $20
return petscii:' ' ; whitspace
if iso_char <= $3f
return iso_char ; numbers and symbols
if iso_char < $80
return translate40to7F[iso_char-$40]
return translateA0toFF[iso_char-$a0]
ubyte[$40] translate40to7F = [
$40, $c1, $c2, $c3, $c4, $c5, $c6, $c7, $c8, $c9, $ca, $cb, $cc, $cd, $ce, $cf,
$d0, $d1, $d2, $d3, $d4, $d5, $d6, $d7, $d8, $d9, $da, $5b, $3f, $5d, $5e, $e4,
$27, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4a, $4b, $4c, $4d, $4e, $4f,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5a, $f3, $7d, $eb, $3f, $3f,
]
ubyte[$60] translateA0toFF = [
$20, $21, $20, $5c, $c5, $d9, $3f, $3f, $3f, $c3, $3f, $28, $3f, $3f, $d2, $e3,
$3f, $3f, $32, $33, $3f, $3f, $ff, $3f, $3f, $31, $3f, $3e, $3f, $3f, $d9, $3f,
$c1, $c1, $c1, $c1, $c1, $c1, $c1, $c3, $c5, $c5, $c5, $c5, $c9, $c9, $c9, $c9,
$c4, $ce, $cf, $cf, $cf, $cf, $cf, $58, $cf, $d5, $d5, $d5, $d5, $d9, $3f, $53,
$41, $41, $41, $41, $41, $41, $41, $43, $45, $45, $45, $45, $49, $49, $49, $49,
$4f, $4e, $4f, $4f, $4f, $4f, $4f, $3f, $4f, $55, $55, $55, $55, $59, $3f, $59,
]
}
asmsub iso2petscii_str(str iso_string @AY) {
; -- convert iso string to petscii (lowercase) string
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
sty P8ZP_SCRATCH_REG
jsr iso2petscii
ldy P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W1),y
iny
bne -
+ rts
}}
}
sub print_bool(bool value) {
+1 -1
View File
@@ -1,4 +1,4 @@
; **experimental** data compression/decompression routines, API subject to change!!
; data compression/decompression routines
; This file contains the shared routines that work on all targets.
compression {
@@ -160,6 +160,9 @@ sub log2(float value) -> float {
ldy #>FL_LOG2_const
jsr MOVFM
jmp FDIVT
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
; !notreached!
}}
}
@@ -302,4 +305,82 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
return v * (outputMax - outputMin) + outputMin
}
asmsub internal_long_AY_to_FAC() {
; Used by the compiler to cast a long to a a float in FAC
; convert the long pointed to by AY into a float and store it in FAC
%asm {{
sta cx16.r1L
sty cx16.r1H
lda #<floats_temp_var
ldy #>floats_temp_var
jsr internal_long_R1_to_float_AY
lda #<floats_temp_var
ldy #>floats_temp_var
jmp MOVFM
}}
}
sub internal_long_R1_to_float_AY() {
; Used by the compiler to cast a long to a a float variable:
; make the float pointed to by AY equal to the long pointed to by R1 (as float)
%asm {{
sta cx16.r0L
sty cx16.r0H
}}
long value = peekl(cx16.r1)
^^ubyte @zp vptr = &value
if value==0 {
sys.memset(cx16.r0, sizeof(float), 0)
return
}
; Determine the sign and work with the absolute value
bool is_negative = value < 0
if is_negative
value = -value
; Calculate the exponent by finding the highest set bit
ubyte highest_bit_pos = internal_get_vptr_highest_bit_pos(vptr)
; For the normalized mantissa, the highest bit is an implicit 1 followed by the rest
; So we shift the absolute value to get the proper mantissa representation
value <<= 31 - highest_bit_pos
; Store the exponent
cx16.r0[0] = highest_bit_pos + 129
; Store the mantissa with the sign bit
cx16.r0[1] = vptr[3] & $7f
if is_negative
cx16.r0[1] |= $80
cx16.r0[2] = vptr[2]
cx16.r0[3] = vptr[1]
cx16.r0[4] = vptr[0]
return
}
sub internal_get_vptr_highest_bit_pos(^^ubyte vptr) -> ubyte {
; note: separate subroutine not nested in internal_long_R1_to_float_AY so that 64tass can optimize it out if not used
if vptr[3]==0
if vptr[2]==0
if vptr[1]==0
if vptr[0]==0 return 0
else return highest_bit_in_byte(vptr[0])
else return 8+highest_bit_in_byte(vptr[1])
else return 16+highest_bit_in_byte(vptr[2])
else return 24+highest_bit_in_byte(vptr[3])
}
asmsub highest_bit_in_byte(ubyte value @A) -> ubyte @Y {
; note: separate subroutine not nested in internal_get_vptr_highest_bit_pos so that 64tass can optimize it out if not used
%asm {{
ldy #0
- lsr a
beq +
iny
bne -
+ rts
}}
}
}
@@ -2,6 +2,52 @@ strings {
; the string functions shared across compiler targets
%option merge, no_symbol_prefixing, ignore_unused
sub split(str s, ^^uword parts, ubyte maxparts) -> ubyte {
; -- split string into parts (splits on whitespace and other non-printable characters).
; Pointers to each part are stored in the given array.
; Returns number of parts found (up to the given maximum). Modifies the string in place!
if s==0 or s[0]==0
return 0
sys.push(cx16.r0L)
alias index = cx16.r0L
ubyte numparts
index = numparts = 0
while numparts < maxparts {
skipwhitespace()
if s[index]!=0 {
parts^^ = s+index
numparts++
parts++
skipchars()
if s[index]==0
break
if numparts < maxparts
s[index] = 0
index++
}
}
if numparts>0 {
parts--
strings.strip(parts^^)
}
cx16.r0L = sys.pop()
return numparts
sub skipwhitespace() {
while s[index]!=0 and (strings.isspace(s[index]) or not strings.isprint(s[index]))
index++
}
sub skipchars() {
while s[index]!=0 and not strings.isspace(s[index])
index++
}
}
sub strip(str s) {
; -- gets rid of whitespace and other non-visible characters at the edges of the string
rstrip(s)
@@ -116,4 +162,31 @@ strings {
sys.clear_carry()
return 255
}
sub next_token(str source, str delimiters) -> str {
; -- Tokenize the source string according to the list of delimiter characters. Like C's ``strtok`` function.
if source == 0
source = last_token_source
else
last_token_source = source
if last_token_source^^ == 0
return 0
while last_token_source^^ != 0 {
^^ubyte dptr = delimiters
while dptr^^ != 0 {
if last_token_source^^ == dptr^^ {
last_token_source^^ = 0
last_token_source += 1
return source
}
dptr += 1
}
last_token_source += 1
}
return source
}
^^ubyte last_token_source
}
@@ -0,0 +1,255 @@
sys {
; the sys functions shared across ALL compiler targets (including the custom defined ones)
%option merge, no_symbol_prefixing, ignore_unused
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
lda P8ZP_SCRATCH_PTR
sta save_SCRATCH_PTR
lda P8ZP_SCRATCH_PTR+1
sta save_SCRATCH_PTR+1
rts
.section BSS
save_SCRATCH_ZPB1 .byte ?
save_SCRATCH_ZPREG .byte ?
save_SCRATCH_ZPWORD1 .word ?
save_SCRATCH_ZPWORD2 .word ?
save_SCRATCH_PTR .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
lda save_prog8_internals.save_SCRATCH_PTR
sta P8ZP_SCRATCH_PTR
lda save_prog8_internals.save_SCRATCH_PTR+1
sta P8ZP_SCRATCH_PTR+1
rts
}}
}
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.
%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!
; If you are on the Commander X16 and have to copy overlapping memory regions, consider using
; the cx16 specific kernal routine `memory_copy` (make sure kernal rom is banked in).
; 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 -
+ lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
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
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_W1
sty P8ZP_SCRATCH_W1+1
ldx P8ZP_SCRATCH_W1+1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (cx16.r0),y
cmp (cx16.r1),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc cx16.r0+1
inc cx16.r1+1
dec P8ZP_SCRATCH_W1+1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_W1
bne +
rts
+ ldy #0
- lda (cx16.r0),y
cmp (cx16.r1),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_W1
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 progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
+231 -5
View File
@@ -295,7 +295,7 @@ _max_too_small
}
asmsub compare(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Compares two strings for sorting, case-sensitively.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (this will use strcmp automatically).
@@ -308,16 +308,81 @@ _max_too_small
}}
}
asmsub ncompare(str string1 @R0, str string2 @AY, ubyte length @X) clobbers(X, Y) -> byte @A {
; Compares two strings for sorting.
asmsub compare_nocase(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting, case-insensitively for PETSCII strings.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Only compares the strings from index 0 up to the length argument.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strncmp_mem
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_loop
lda (P8ZP_SCRATCH_W1),y
beq _c1_zero
jsr lowerchar
sta P8ZP_SCRATCH_REG
lda (P8ZP_SCRATCH_W2),y
jsr lowerchar
cmp P8ZP_SCRATCH_REG
beq _equal
bpl _less
lda #1
rts
_less
lda #-1
rts
_equal
iny
bne _loop
_c1_zero
lda (P8ZP_SCRATCH_W2),y
beq +
lda #-1
+ rts
}}
}
asmsub compare_nocase_iso(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting, case-insensitively for ISO strings.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_loop
lda (P8ZP_SCRATCH_W1),y
beq _c1_zero
jsr lowerchar_iso
sta P8ZP_SCRATCH_REG
lda (P8ZP_SCRATCH_W2),y
jsr lowerchar_iso
cmp P8ZP_SCRATCH_REG
beq _equal
bpl _less
lda #1
rts
_less
lda #-1
rts
_equal
iny
bne _loop
_c1_zero
lda (P8ZP_SCRATCH_W2),y
beq +
lda #-1
+ rts
}}
}
@@ -344,6 +409,26 @@ _done rts
}}
}
asmsub lower_iso(str st @AY) -> ubyte @Y {
; Lowercases the ISO string in-place. Returns length of the string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
cmp #65
bcc +
cmp #91
bcs +
ora #%00100000
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
asmsub upper(str st @AY) -> ubyte @Y {
; Uppercases the petscii string in-place. Returns length of the string.
%asm {{
@@ -364,7 +449,28 @@ _done rts
}}
}
asmsub upper_iso(str st @AY) -> ubyte @Y {
; Uppercases the ISO string in-place. Returns length of the string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
cmp #97
bcc +
cmp #123
bcs +
and #%11011111
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
asmsub lowerchar(ubyte character @A) -> ubyte @A {
; to petscii lower case
%asm {{
and #$7f
cmp #97
@@ -376,7 +482,20 @@ _done rts
}}
}
asmsub lowerchar_iso(ubyte character @A) -> ubyte @A {
; to ISO lowercase
%asm{{
cmp #65
bcc +
cmp #91
bcs +
ora #$20
+ rts
}}
}
asmsub upperchar(ubyte character @A) -> ubyte @A {
; to petscii upper case
%asm {{
cmp #65
bcc +
@@ -387,8 +506,21 @@ _done rts
}}
}
asmsub upperchar_iso(ubyte character @A) -> ubyte @A {
; to ISO uppercase
%asm{{
cmp #97
bcc +
cmp #121
bcs +
and #%11011111
+ rts
}}
}
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> bool @A {
; This routine matches a string against a pattern and returns with the carry bit set if they match, or clear if they don't.
; The matching is CASE-SENSITIVE. If you want to match case-insensitively, use pattern_match_nocase().
; The two characters ? and * have a special meaning when they appear in the pattern. All other characters match themselves.
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
; * matches any string, including the empty string.
@@ -441,6 +573,100 @@ reg cmp (strptr),y ; are both characters the same?
bne next ; not yet, loop
found rts ; success, return with c=1
star inx ; skip star in pattern
modify_pattern2
cmp $ffff,x ; string of stars equals one star MODIFIED
beq star ; so skip them also
stloop txa ; we first try to match with * = ""
pha ; and grow it by 1 character every
tya ; time we loop
pha ; save x and y on stack
jsr next ; recursive call
pla ; restore x and y
tay
pla
tax
bcs found ; we found a match, return with c=1
iny ; no match yet, try to grow * string
lda (strptr),y ; are we at the end of string?
bne stloop ; not yet, add a character
fail clc ; yes, no match found, return with c=0
rts
}}
}
asmsub pattern_match_nocase(str string @AY, str lowercase_pattern @R0, bool iso @Pc) clobbers(Y) -> bool @A {
; This routine matches a string against a pattern and returns with the carry bit set if they match, or clear if they don't.
; The matching is CASE-INSENSITIVE. If you want a slightly faster, but case-sensitive matching, use pattern_match().
; The two characters ? and * have a special meaning when they appear in the pattern. All other characters match themselves.
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
; * matches any string, including the empty string.
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
; Code taken from http://6502.org/source/strings/patmatch.htm
;
; Input: cx16.r0: A NUL-terminated, <255-length pattern
; AY: A NUL-terminated, <255-length string
; Carry : SET for ISO encoding, CLEAR for petscii encoding
;
; Output: A = 1 if the string matches the pattern, A = 0 if not.
;
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
; Does not work in ROM due to self-modifying code.
%asm {{
strptr = P8ZP_SCRATCH_W1
sta strptr
sty strptr+1
lda cx16.r0
sta modify_pattern1+1
sta modify_pattern2+1
lda cx16.r0+1
sta modify_pattern1+2
sta modify_pattern2+2
bcs +
lda #<lowerchar
ldy #>lowerchar
bcc ++
+ lda #<lowerchar_iso
ldy #>lowerchar_iso
+ sta modify_lowerchar+1
sty modify_lowerchar+2
jsr _match
lda #0
rol a
rts
_match
ldx #$00 ; x is an index in the pattern
ldy #$ff ; y is an index in the string
modify_pattern1
next lda $ffff,x ; look at next pattern character MODIFIED
cmp #'*' ; is it a star?
beq star ; yes, do the complicated stuff
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
bne reg ; no, it's a regular character
lda (strptr),y ; yes, so it will match anything
beq fail ; except the end of string
reg
sta P8ZP_SCRATCH_B1
lda (strptr),y
modify_lowerchar
jsr $ffff ; modified
cmp P8ZP_SCRATCH_B1 ; are both characters the same (lowercased)?
bne fail ; no, so no match
inx ; yes, keep checking
cmp #0 ; are we at end of string?
bne next ; not yet, loop
found rts ; success, return with c=1
star inx ; skip star in pattern
modify_pattern2
cmp $ffff,x ; string of stars equals one star MODIFIED
+20
View File
@@ -279,6 +279,26 @@ sub hex2uword(str string) -> uword {
}
}
sub hex2long(str string) -> long {
; -- hexadecimal string (with or without '$') to long.
; stops parsing at the first character that's not a hex digit (except leading $)
long result
ubyte char
if @(string)=='$'
string++
repeat {
char = @(string)
if char==0
return result
result <<= 4
if char>='0' and char<='9'
result |= char-'0'
else
result |= char-'a'+10
string++
}
}
sub bin2uword(str string) -> uword {
; -- binary string (with or without '%') to uword.
; stops parsing at the first character that's not a 0 or 1. (except leading %)
+38
View File
@@ -441,4 +441,42 @@ math {
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
sub gcd(uword aa, uword bb) -> uword {
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm).
; Handle the case where one number is zero
if aa == 0 return bb
if bb == 0 return aa
; Count the number of common factors of 2
ubyte shift = 0
; Count common factors of 2 by shifting both numbers right until at least one is odd
while ((aa | bb) & 1) == 0 {
aa >>= 1
bb >>= 1
shift++
}
; Remove remaining factors of 2 from a
while (aa & 1) == 0
aa >>= 1
; Now a is odd. Loop until b becomes zero
while bb != 0 {
; Remove remaining factors of 2 from b
while (bb & 1) == 0
bb >>= 1
; Make sure a <= b
if aa > bb
swap(aa, bb)
bb -= aa
}
; Return the result multiplied by the common factors of 2
return aa << shift
}
}
+18 -12
View File
@@ -37,7 +37,7 @@ strings {
for ix in 0 to slen-1 {
target[ix] = source[ix+offset]
}
target[ix]=0
target[slen]=0
}
sub slice(str source, ubyte start, ubyte slen, str target) {
@@ -50,7 +50,7 @@ strings {
for ix in 0 to slen-1 {
target[ix] = source[ix+start]
}
target[ix]=0
target[slen]=0
}
sub find(str st, ubyte character) -> ubyte, bool {
@@ -145,7 +145,7 @@ strings {
}
sub compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Compares two strings for sorting (case sensitive).
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (this will use strcmp automatically).
@@ -157,21 +157,21 @@ strings {
}}
}
sub ncompare(str st1, str st2, ubyte length) -> byte {
; Compares two strings for sorting.
sub compare_nocase(str st1, str st2) -> byte {
; Compares two strings for sorting (case insensitive).
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Only compares the strings from index 0 up to the length argument.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (this will use strcmp automatically).
%ir {{
loadm.w r99000,strings.ncompare.st1
loadm.w r99001,strings.ncompare.st2
loadm.b r99100,strings.ncompare.length
syscall 58 (r99000.w, r99001.w, r99100.b) : r99100.b
loadm.w r99000,strings.compare_nocase.st1
loadm.w r99001,strings.compare_nocase.st2
syscall 58 (r99000.w, r99001.w) : r99100.b
returnr.b r99100
}}
}
sub lower(str st) -> ubyte {
; Lowercases the petscii string in-place. Returns length of the string.
; Lowercases the ISO string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
ubyte ix
@@ -186,7 +186,7 @@ strings {
}
sub upper(str st) -> ubyte {
; Uppercases the petscii string in-place. Returns length of the string.
; Uppercases the ISO string in-place. Returns length of the string.
ubyte ix
repeat {
ubyte char=st[ix]
@@ -210,6 +210,12 @@ strings {
return char
}
alias lowerchar_iso = strings.lowerchar
alias upperchar_iso = strings.upperchar
alias lower_iso = strings.lower
alias upper_iso = strings.upper
alias compare_nocase_iso = strings.compare_nocase
sub hash(str st) -> ubyte {
; experimental 8 bit hashing function.
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
+18
View File
@@ -266,6 +266,14 @@ sys {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
sub progstart() -> uword {
return $1000 ; just a dummy value
}
sub progend() -> uword {
return $c000 ; just a dummy value
}
}
cx16 {
@@ -307,6 +315,16 @@ cx16 {
&word r14s = $ff1e
&word r15s = $ff20
; signed long versions
&long r0r1sl = $ff02
&long r2r3sl = $ff06
&long r4r5sl = $ff0a
&long r6r7sl = $ff0e
&long r8r9sl = $ff12
&long r10r11sl = $ff16
&long r12r13sl = $ff1a
&long r14r15sl = $ff1e
; ubyte versions (low and high bytes)
&ubyte r0L = $ff02
&ubyte r1L = $ff04
+34
View File
@@ -220,4 +220,38 @@ sub petscii2scr_str(str petscii_string) {
}
}
sub iso2petscii(ubyte iso_char) -> ubyte {
; --converts iso 8859-15 character to petscii character (lowercase)
if iso_char & $7f <= $20
return petscii:' ' ; whitspace
if iso_char <= $3f
return iso_char ; numbers and symbols
if iso_char < $80
return translate40to7F[iso_char-$40]
return translateA0toFF[iso_char-$a0]
ubyte[$40] translate40to7F = [
$40, $c1, $c2, $c3, $c4, $c5, $c6, $c7, $c8, $c9, $ca, $cb, $cc, $cd, $ce, $cf,
$d0, $d1, $d2, $d3, $d4, $d5, $d6, $d7, $d8, $d9, $da, $5b, $3f, $5d, $5e, $e4,
$27, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4a, $4b, $4c, $4d, $4e, $4f,
$50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5a, $f3, $7d, $eb, $3f, $3f,
]
ubyte[$60] translateA0toFF = [
$20, $21, $20, $5c, $c5, $d9, $3f, $3f, $3f, $c3, $3f, $28, $3f, $3f, $d2, $e3,
$3f, $3f, $32, $33, $3f, $3f, $ff, $3f, $3f, $31, $3f, $3e, $3f, $3f, $d9, $3f,
$c1, $c1, $c1, $c1, $c1, $c1, $c1, $c3, $c5, $c5, $c5, $c5, $c9, $c9, $c9, $c9,
$c4, $ce, $cf, $cf, $cf, $cf, $cf, $58, $cf, $d5, $d5, $d5, $d5, $d9, $3f, $53,
$41, $41, $41, $41, $41, $41, $41, $43, $45, $45, $45, $45, $49, $49, $49, $49,
$4f, $4e, $4f, $4f, $4f, $4f, $4f, $3f, $4f, $55, $55, $55, $55, $59, $3f, $59,
]
}
sub iso2petscii_str(str iso_string) {
; -- convert iso string to petscii (lowercase) string
cx16.r0 = iso_string
while @(cx16.r0)!=0 {
@(cx16.r0) = iso2petscii(@(cx16.r0))
cx16.r0++
}
}
}
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff. Show more