Compare commits

...
267 Commits
Author SHA1 Message Date
Irmen de Jong 0dd1c17ff4 avoid possible crash 2024-10-13 17:51:14 +02:00
Irmen de Jong fa5479ee5f fix ast printing of arrays with duplicate elements 2024-10-13 04:30:46 +02:00
Irmen de Jong 03412cacba added examples/cx16/balloonflight.p8 2024-10-13 00:51:07 +02:00
Irmen de Jong 01a38a0b11 Merge branch 'monogfx_fill_optimization' 2024-10-12 17:29:38 +02:00
Irmen de Jong f43c14bd78 doc 2024-10-12 17:29:28 +02:00
Irmen de Jong fb23452383 optimize monogfx.fill() 2024-10-12 17:18:00 +02:00
Irmen de Jong ab7dde1450 todo 2024-10-12 13:04:19 +02:00
Irmen de Jong 8d9bc2f5ff fixing all sorts of things about assigning arrays to arrays 2024-10-12 12:33:46 +02:00
Irmen de Jong 7651ccc84e fix a type error 2024-10-11 00:50:05 +02:00
Irmen de Jong 1a6b95b388 house cleaning 2024-10-10 20:46:18 +02:00
Irmen de Jong 78ec1e7512 version 2024-10-09 22:21:04 +02:00
Irmen de Jong 7e38d26c33 added several color fade functions to the palette module (cx16) 2024-10-09 21:48:04 +02:00
Irmen de Jong ed09dd4e9e improve automatic type conversions for return values, fixes #155 2024-10-09 20:04:05 +02:00
Irmen de Jong 5731b79554 don't allow problematic string and array assignments anymore, improve error messages.
In certain cases you will need to use string.copy() explicitly to overwrite strings with new strings.
2024-10-09 00:51:05 +02:00
Irmen de Jong eaa22a9d13 added callfar2() builtin function that allows to set A,X,Y and Carry arguments. 2024-10-08 21:36:04 +02:00
Irmen de Jong b2bdfe8482 fix ir rndseed() 2024-10-08 20:40:00 +02:00
Irmen de Jong fea531be9a add sys.sizeof_bool, _ubyte, _uword constants 2024-10-07 20:45:13 +02:00
Irmen de Jong 7c69d38588 scan all asmsubs to see if another subroutine is referenced. Fixes #153 2024-10-07 20:39:49 +02:00
Irmen de Jong a088ee56b0 function inlining can no longer get into an infinite loop. Fixes #154 2024-10-07 19:58:04 +02:00
Irmen de Jong ae669af904 add sys.sizeof_byte, _word, _float constants
because Antlr doesn't allow the grammar to contain a sizeof(typename) rule to override the sizeof(identifier) rule
2024-10-07 19:17:37 +02:00
Irmen de Jong d1ddf05e38 check that block address leaves room for program startup logic 2024-10-03 22:30:06 +02:00
Irmen de Jong 51279a98b3 attempt to fix forloop range datatype issues 2024-10-03 21:12:31 +02:00
Irmen de Jong bf33a4f82d small refactor to prepare for better range dt adjustment 2024-10-02 23:28:33 +02:00
Irmen de Jong fff0d741c3 improved parsing of "not in" operator, and [] array signature (allow space) 2024-10-02 19:06:20 +02:00
Irmen de Jong e83d0ee820 fix crash in msb() when assigning to word again. Fix wrong register in lsb() and msb() in certain situations. 2024-10-02 02:40:16 +02:00
Irmen de Jong 09f3eecf56 changed cx16/rotating-stars example to starszoom instead. 2024-10-02 01:36:54 +02:00
Irmen de Jong 2bd4326ff6 added cx16/rotating-stars example 2024-10-01 23:43:50 +02:00
Irmen de Jong c13168b60c various improvements:
fix verafx.available().
added gfx_lores.plot().
faster gfx_lores.clear_screen().
added a new Sublime Text 4 syntax highlighting file.
2024-10-01 22:18:03 +02:00
Irmen de Jong ea3871d0c4 comment about builtin function call ast node type 2024-10-01 02:14:31 +02:00
markjreed 70a2b11271 New example program: draw a fractal tree (#152)
* demo of prog8 recursion to draw a fractal tree

* feat: comments

* fix: comment formatting

* feat: make minimum branch size a tunable parameter
2024-10-01 02:00:04 +02:00
Irmen de Jong 3cf39e072e fix C64 floating point sign issue 2024-09-30 21:56:34 +02:00
Irmen de Jong 413b86cc4a more helpful error messages 2024-09-30 00:27:29 +02:00
Irmen de Jong a6107fcfdf taking the address of a romsub is now the constant value of said romsub's declared address 2024-09-29 23:53:39 +02:00
Irmen de Jong a064ade1e0 better codegen for call() function 2024-09-29 23:18:51 +02:00
Irmen de Jong df35aa7942 added (experimental) compression library 2024-09-29 18:59:53 +02:00
Irmen de Jong cd49c5f88d cx16: set rom bank to 0 at startup (for faster kernal API calls)
cx16: callfar() with constant address generates shorter asm
2024-09-28 20:58:29 +02:00
Irmen de Jong 1541ad2160 fix variable init order mistake in program startup
reset multiply bit at end of verafx.muls
2024-09-28 19:17:45 +02:00
Irmen de Jong c78b7b1a24 added verafx.mult16() 2024-09-28 01:00:28 +02:00
Irmen de Jong 9c7a645e18 remove non-functional verafx.mult(). note: muls() is still there and just fine!
added documentation/source code comments to the cpu word*word multiplication routine not producing the correct upper 16 bits.
2024-09-28 00:43:05 +02:00
Irmen de Jong 4acf38031a feedback 2024-09-27 20:59:31 +02:00
Irmen de Jong 4cd7271e30 added prog8 vs other languages chapter to the manual 2024-09-27 20:19:28 +02:00
Irmen de Jong 3f630ab1b0 RAW output now also properly initializes variables 2024-09-27 18:46:03 +02:00
Irmen de Jong 04cb684fd4 tweak program start initialization and fix cleanup at exit for atari and pet compiler targets 2024-09-27 02:14:54 +02:00
Irmen de Jong 4c843571ea fix syntax error check for missing return statement 2024-09-26 01:52:33 +02:00
Irmen de Jong 1326498802 update skeleton scripts 2024-09-26 00:12:29 +02:00
Irmen de Jong b7ebd8c4a6 update cx16/audioroutines example to use the new audio module 2024-09-26 00:08:25 +02:00
markjreed 24e0a69480 feat: module with front-ends that jsrfar into audio ROM routines (#151) 2024-09-26 00:00:42 +02:00
Irmen de Jong 4bcb2bdede added benchmark program 2024-09-25 23:32:45 +02:00
Irmen de Jong d27f3eb8a4 remove wrong print_f mention 2024-09-24 23:58:39 +02:00
Irmen de Jong d3e4481112 fix asm optimizer bug where it erroneously discarded rts with a label 2024-09-22 21:41:41 +02:00
Irmen de Jong 1d1d6b3d98 tweak c64 balloonflight example etc. 2024-09-22 13:20:12 +02:00
Irmen de Jong 90b8a22a71 correct amount 2024-09-20 22:35:17 +02:00
Irmen de Jong 8dbfb8ab76 move community note to start page 2024-09-20 20:12:33 +02:00
Irmen de Jong e29ff1c848 fix name redefinition check for multi-declarations 2024-09-20 19:55:32 +02:00
Irmen de Jong 585f6ffc9b version 10.4.1 2024-09-20 18:43:08 +02:00
Irmen de Jong 46b94c17d6 comment 2024-09-20 17:33:00 +02:00
Irmen de Jong 7af8007447 Merge remote-tracking branch 'origin/master' 2024-09-20 17:28:55 +02:00
markjreed 16a2b2f566 Add 24-bit integer-to-float routine and floating-point jiffy clock reader (#150)
* feat: add routine to convert 24-bit integer from A/X/Y to float in FAC1

* fix: remove duplicate definition

* fix: shift to appropriate exponent
2024-09-20 17:28:37 +02:00
Irmen de Jong ea2a90c3c5 nah 2024-09-19 23:09:59 +02:00
Irmen de Jong 5cda750e5e improve error message for undefined symbol in when choices 2024-09-18 23:00:03 +02:00
Irmen de Jong 4e143d45c8 fix warning 2024-09-18 22:04:25 +02:00
Irmen de Jong 4c50980d81 new skeletons dump 2024-09-18 18:45:43 +02:00
Irmen de Jong 2954f5f04d Add clear_screen and set_screen_mode to gfx_lores. Fix boolean draw vs color param in some monogfx routines. Elaborate some docs. 2024-09-17 22:19:47 +02:00
Irmen de Jong cac4c1eb1e improve callgraph unused subroutine check for routines called from inline asm 2024-09-16 21:49:30 +02:00
Irmen de Jong 0b1f30d98c no more span overdraws in graphics disc routines 2024-09-15 15:38:33 +02:00
Irmen de Jong c7b1e8d772 fixed a variable scopedname issue where it took the fully scoped name instead of just the local name
this made 64tass not strip out that code if it was unused
2024-09-14 23:17:26 +02:00
Irmen de Jong a4f7512d44 oops, was fixed 2024-09-14 22:33:20 +02:00
Irmen de Jong 0d3ad80659 retain type of consts better to avoid precision loss
this also fixed a difference in const calculation where the result could differ if you were using optimzations or not.
2024-09-14 21:06:21 +02:00
Irmen de Jong aba1a73e28 actually use any @zp etc tags on subroutine parameters 2024-09-14 17:42:13 +02:00
Irmen de Jong dca31b2ca3 added gfx_lores module for cx16 for optimized graphics routines for lores 256c screen mode
currently contains a new line() routine
2024-09-14 15:30:39 +02:00
Irmen de Jong 0cb378ca31 added emudbg.cpu_cycles() and emudbg.reset_cpu_cycles() 2024-09-13 23:03:14 +02:00
Irmen de Jong cf551d2cc7 tweak containment check even more 2024-09-13 00:02:26 +02:00
Irmen de Jong ac0c8a68f6 IR: Improve codegen for for loops downto 0/1 2024-09-12 23:00:32 +02:00
Irmen de Jong 5986dcdd2f add new containment check codegen for IR 2024-09-12 22:04:20 +02:00
Irmen de Jong 6be6eb2227 tweak diskio to not always include unused subroutine internal_f_tell() in resulting program 2024-09-11 19:51:53 +02:00
Irmen de Jong d34015eec5 fix gfx2.fill() vera CTRL corruption 2024-09-11 19:10:45 +02:00
Irmen de Jong 255c5bfaca improve containment check for few values 2024-09-11 03:24:30 +02:00
Irmen de Jong 01c6754928 get rid of problematic common-subexpression optimization 2024-09-11 01:10:42 +02:00
Irmen de Jong 8eaf884f69 improve codegen for for loops downto 0,1 when start value is not const 2024-09-10 23:54:44 +02:00
Irmen de Jong 699a2bb7ab improved codegen for for loops downto 0 2024-09-10 21:33:57 +02:00
Irmen de Jong 4a2dcd20d1 fix the "x<2" optimization made a few commits ago to only work on unsigned 2024-09-09 23:06:36 +02:00
Irmen de Jong 4e98fb75d6 support assigning multiple return flags from asmsub in 6502 codegen 2024-09-09 22:56:40 +02:00
Irmen de Jong 64e66e732f cx16/circles example now uses gfx2 2024-09-08 22:52:46 +02:00
Irmen de Jong 7aec627f6b add optimization if x==0 or x==1 -> if x<2 2024-09-08 22:39:48 +02:00
Irmen de Jong 59a2fec176 fix IR containment check 2024-09-08 21:49:13 +02:00
Irmen de Jong edc5a5a94f improve data driven unit tests to use kotest withData() 2024-09-08 16:55:08 +02:00
Irmen de Jong c5b7edad82 added memsizer unit tests 2024-09-08 15:24:47 +02:00
Irmen de Jong 124ffac4e4 readme 2024-09-08 11:50:18 +02:00
Irmen de Jong 6d2a36fb2b testcase improvement 2024-09-06 22:51:26 +02:00
Irmen de Jong 28b43b3e1d added cx16.EXTAPI_kbd_leds definition (new in kernal R48) 2024-09-06 20:47:49 +02:00
Irmen de Jong f7feaf158d added cx16.mouse_present() routine to check for presence of mouse 2024-09-06 18:21:13 +02:00
Irmen de Jong 2396f707c6 fix bug in codegen for certain array lookups using word typed index value (i.e. via a pointer variable) 2024-09-06 18:11:41 +02:00
Irmen de Jong d4d8e1b1ba comment about implementation in life example 2024-09-06 16:39:44 +02:00
Irmen de Jong 44fec2c729 some additional last minute optimization to life example 2024-09-06 15:56:41 +02:00
Irmen de Jong a80a6913e3 some additional last minute tweaks to life example 2024-09-06 15:42:49 +02:00
Irmen de Jong 0eac04c220 added cx16/life.p8 example (Conway's game of life) 2024-09-06 15:21:29 +02:00
Irmen de Jong 29dd758302 Fix compiler crash in for loops with just 1 iteration 2024-09-05 21:26:46 +02:00
Irmen de Jong 5c45adc7f0 graphics module on x16 now uses kernal (R48 or newer) support for drawing circles and ovals 2024-09-05 20:27:12 +02:00
Irmen de Jong ad22cf08cd todo 2024-09-03 17:46:32 +02:00
Irmen de Jong 2c2ae64194 replace java Stack by kotlin ArrayDeque 2024-09-02 00:15:28 +02:00
Irmen de Jong 97c2dadd16 doc update 2024-09-01 20:55:43 +02:00
Irmen de Jong b36e1e3baf change sprite.hide() : now disables sprite instead of moving it offscreen.
added sprite.show() to re-enable it (with z-order 3, as all sprites have by default in this module)
added sprite.zdepth() to set a custom z-depth.
2024-09-01 20:55:43 +02:00
Irmen de Jong 2da35fec17 remove requirement to end subroutine with an EOL, so oneliners are now possible
main { sub start() { cx16.r0++ cx16.r1++ } }
2024-09-01 20:55:43 +02:00
Irmen de Jong bdeac74cfc removed the -nostrictbool compiler option
boolean types and bytes are no longer implicitly interchangeable using this option
2024-09-01 20:53:39 +02:00
Irmen de Jong 6516d7cb15 regenerate skeletons and set version 10.4 2024-08-25 16:56:33 +02:00
Irmen de Jong 31cf76042d scope 2024-08-25 15:28:45 +02:00
Irmen de Jong c4c4dcf2b3 optimizing gfx2.fill() 4 color mode 2024-08-25 15:05:24 +02:00
Irmen de Jong 03145630f8 optimizing gfx2.fill() 2024-08-25 14:26:49 +02:00
Irmen de Jong e2fcac322f optimizing gfx2.fill() 2024-08-25 13:51:01 +02:00
Irmen de Jong beaff4d650 moved non X16 specific variables and vector definitions from cx16 to cbm namespace.
This makes the naming consistent with the other cbm-like targets (c64, pet, c128). Only the x16 specific ones remain in the cx16 namespace, such as cx16.KEYHDL

Probably the most impactful is the move of cx16.CINV to cbm.CINV
2024-08-24 20:06:50 +02:00
Irmen de Jong 79af96ddde new kotlin version 2024-08-24 16:34:26 +02:00
Irmen de Jong e439720c9d optimized string compares 2024-08-24 14:53:18 +02:00
Irmen de Jong 48d0185ea4 increase flood fill stack size a bit 2024-08-23 20:55:30 +02:00
Irmen de Jong e2592b4e0b fix possible gfx2 color problem in 2bpp mode 2024-08-23 19:48:23 +02:00
Irmen de Jong 2967866e3d avoid self-modifying code to be compatible with IRQ handlers 2024-08-23 17:46:23 +02:00
Irmen de Jong b566ea5c3f added string.rfind() 2024-08-22 23:22:31 +02:00
Irmen de Jong 8f6eaeac2c half width katakana conversion 2024-08-21 18:51:34 +02:00
Irmen de Jong b4facaeb3c add "kata" string encoding (Katakana) 2024-08-20 21:40:43 +02:00
Irmen de Jong d12b7ccc6b fix syslib importing for raw outputs. fixes #144 2024-08-19 13:33:02 +02:00
Irmen de Jong 453e8bd0a0 update kotlin and antlr libs 2024-08-19 12:47:18 +02:00
Irmen de Jong 9204d390ae correct fetch() signature. fixes #148 2024-08-18 20:04:23 +02:00
Irmen de Jong b70ce0015c fix missing opportunities to use TSB instruction 2024-07-24 22:51:49 +02:00
Irmen de Jong d113827753 todo 2024-07-24 19:50:30 +02:00
Irmen de Jong c67f877857 Codegen: use BIT instruction for memory location bit 7 and 6 tests (use N and V flags) 2024-07-24 19:26:54 +02:00
Irmen de Jong 0ec719e429 cx16: added a polling pcm streaming example 2024-07-23 21:37:11 +02:00
Irmen de Jong 17f7b11148 tweaks cx16 sample streaming example, also added a new one 2024-07-23 02:10:05 +02:00
Irmen de Jong 966b017670 tweaks 2024-07-22 18:20:01 +02:00
Irmen de Jong 4c98070b3c optimize shifts by 1 by inlining it better 2024-07-21 22:08:41 +02:00
Irmen de Jong 3681d6ee1c optimize division by powers of 2 better (into bit shifts) 2024-07-21 21:34:38 +02:00
Irmen de Jong 0af17cdc33 todo's for division optimizations 2024-07-21 20:32:03 +02:00
Irmen de Jong 2aae1f5e30 stricter checks for negative array indexing 2024-07-20 22:37:03 +02:00
Irmen de Jong d18f2a7bfd improved codegen for some pointer+index expressions 2024-07-18 23:41:34 +02:00
Irmen de Jong 9046fe8d3a ringbuffer and pointer optimization todo 2024-07-16 22:59:31 +02:00
Irmen de Jong 78c7ee247a generate 65c02 TSB/TRB instructions in certain cases 2024-07-16 00:36:00 +02:00
Irmen de Jong d5adb85e5b IR: add SEC,CLC,SEI,CLI instructions for the sys function calls. 2024-07-14 21:01:19 +02:00
Irmen de Jong 69f953fd9b diskio.f_readline() now also returns I/O status as secondary return value in A 2024-07-06 22:25:01 +02:00
Irmen de Jong 484677b4b1 Get rid of any() and all() builtin functions.
Replaced by regular subroutines in the anyall module.
2024-07-06 18:49:03 +02:00
Irmen de Jong b10a8e728f update vim syntax too 2024-07-06 17:13:42 +02:00
Irmen de Jong 25f25a8767 Get rid of sort() and reverse() builtin functions.
Sort() had too many gotchas and reverse() is kinda redundant you can loop in decreasing order through an array too.
2024-07-06 17:07:58 +02:00
Irmen de Jong 0c053e4a2c IR: don't confuse symbol names starting with 'r', with register names
Added start of buffer.p8 (experimental)
2024-07-04 01:39:25 +02:00
Irmen de Jong 3f6521cc9b todo 2024-07-03 20:33:59 +02:00
Irmen de Jong a074491d5b fix doc build 2024-07-03 00:08:49 +02:00
Irmen de Jong a291164953 fix leaving Vera CTRL at 1 instead of 0, could lead to kernal text output errors etc
Fixes #143
2024-07-02 23:36:36 +02:00
Irmen de Jong 43c55b58d2 fix register overwriting for certain subroutine call parameter combinations. Fixes #136 2024-07-02 23:26:34 +02:00
Irmen de Jong e7298f8162 fix invalid code gen for if v1==0 or v2==0 2024-07-01 23:38:25 +02:00
Irmen de Jong ddf990296b fix subroutine inlining symbol scope error 2024-06-29 18:53:54 +02:00
Irmen de Jong ead8aa7800 asm optimization: bxx+jmp -> opposite bxx 2024-06-29 17:22:57 +02:00
Irmen de Jong 7a9dd1ac9b optimize trivial 65c02 stack instructions 2024-06-29 15:42:40 +02:00
Irmen de Jong 1c97c22eff optimize simple word and byte addition/subtraction better 2024-06-29 14:33:42 +02:00
Irmen de Jong bbf621a8c4 doc 2024-06-29 13:39:08 +02:00
Irmen de Jong 8efa89165c sprites.get_data_ptr() signature changed: now properly returns the 2 values 2024-06-27 22:22:26 +02:00
Irmen de Jong 4f8aaf9244 some tweaks and todos 2024-06-26 21:22:40 +02:00
Irmen de Jong a97edef380 update gradle wrapper to gradle 8.8 2024-06-25 22:43:04 +02:00
Irmen de Jong eefae24aa3 update gradle wrapper to gradle 8.8 2024-06-25 22:42:16 +02:00
Irmen de Jong 54bffc91ae properly generate PUSH and POP instructions for push() and pop() calls in IR.
Also switch to a fork of shadowJar to avoid Gradle deprecation errors.
2024-06-25 22:39:20 +02:00
Irmen de Jong 63f5ef9e14 fix typo for bool array storage size 2024-06-20 22:55:47 +02:00
Irmen de Jong 034f27a8dd added queens example, update kotest lib 2024-06-19 23:57:43 +02:00
Irmen de Jong c2f6311367 todo 2024-06-17 22:41:52 +02:00
markjreed 6f00a48772 fix: atan2(anything, 0) should return ±π/2 (#141)
* fix: atan2(anything, 0) should return pi/2

* fix: if y<0, x=0 maps to 3π/2, not π/2

* fix: standard seems to be atan2(0,0) == 0
2024-06-07 23:19:45 +02:00
Irmen de Jong b3dba67405 added cx16.rom_version() routine 2024-06-07 23:15:26 +02:00
Irmen de Jong c9a4235669 update to kotlin 2.0, fix several code style issues 2024-06-04 01:00:46 +02:00
Irmen de Jong ae0d52274c Merge branch 'refs/heads/fixwindowseolstests' 2024-06-04 00:13:55 +02:00
Irmen de Jong 8973763866 Fix line endings conversion errors on windows builds 2024-06-04 00:12:12 +02:00
Irmen de Jong 3d799ae7fe todo 2024-06-01 15:03:01 +02:00
Irmen de Jong 8b10115390 release 10.3.1 2024-05-31 23:51:35 +02:00
Irmen de Jong d2e010c439 added cx16.scnsiz (extapi call), describe profiler.py script 2024-05-31 21:48:29 +02:00
Irmen de Jong 15867ab423 update cx16.mouse_get() and mouse_pos() to also return scroll wheel in X 2024-05-29 23:19:53 +02:00
Irmen de Jong 22c9e99fa3 explain integer math sin/cos routines even better 2024-05-29 23:12:00 +02:00
Irmen de Jong ee262f6aad explain integer math sin/cos routines even better 2024-05-29 20:26:42 +02:00
Irmen de Jong af64af2397 explain integer math sin/cos routines better 2024-05-29 19:48:27 +02:00
Irmen de Jong 1feead2260 tweaks 2024-05-29 02:30:06 +02:00
Irmen de Jong d3dcd24b4d add profiler script 2024-05-29 00:56:31 +02:00
Irmen de Jong fd1e6796ef correct branch instruction, fixes #137 2024-05-24 20:54:40 +02:00
Irmen de Jong 3ea0f0cbaa remove 16 bit f_tell variant. 2024-05-22 21:47:02 +02:00
Irmen de Jong f3e3311598 added diskio.f_tell() and f_tell32() on the cx16 target 2024-05-21 23:14:25 +02:00
Irmen de Jong 0dc50a93a4 added @nozp variable flag 2024-05-21 21:53:58 +02:00
Irmen de Jong fda8e61be4 give better error when using @split wrong 2024-05-20 21:51:07 +02:00
Irmen de Jong ac1d4b4a7a mouse_pos() now returns the coordinates as unsigned words 2024-05-20 21:38:02 +02:00
Irmen de Jong c719e274d5 java version tweaks 2024-05-18 20:25:44 +02:00
Irmen de Jong e4990f8ec5 Revert "update to Java 17 LTS"
This reverts commit 3ef5bdfeda.
2024-05-18 18:59:32 +02:00
Irmen de Jong 62afd3342e void syntax check, fixes #135 2024-05-18 17:15:31 +02:00
Irmen de Jong 6e8a89e6f1 optimize const word repeat setup 2024-05-18 16:30:27 +02:00
Irmen de Jong aa2437cfb8 fix invalid repeat loop when iterations is already in register Y 2024-05-18 15:09:56 +02:00
Irmen de Jong 4a710ecdfc cleanups 2024-05-17 18:48:04 +02:00
Irmen de Jong 3ef5bdfeda update to Java 17 LTS 2024-05-17 18:27:21 +02:00
Irmen de Jong 7915dda35f update libraries 2024-05-12 03:02:54 +02:00
Irmen de Jong 9120e16683 todo 2024-05-02 21:02:50 +02:00
Irmen de Jong a1ebc7090d fix sieve example 2024-04-18 22:22:29 +02:00
Irmen de Jong 054b4636e0 version 10.3 2024-04-18 21:50:48 +02:00
Irmen de Jong e3e7b060b7 vumeter tweaks 2024-04-18 01:31:59 +02:00
Irmen de Jong 5ac9c75521 docs of new floats routines and added them to VM target too 2024-04-17 20:03:36 +02:00
markjreed 07710e0995 Feature/reciprocal tangent functions (#133)
* feat: additional trig functions

* fix: 64tass won't assemble a proc named 'sec'

* fix: indentation
2024-04-17 19:54:47 +02:00
Irmen de Jong d6a67f5f2b vumeter colors 2024-04-17 00:22:19 +02:00
Irmen de Jong 2675623aea fix optimization ast parent linkage problem 2024-04-16 23:27:22 +02:00
Irmen de Jong 94263c43d0 added cx16/vumeter example 2024-04-16 22:48:36 +02:00
Irmen de Jong d8ec03874f move the pi-related constants from system specific floats module into the shared one. Clarify some stuff. 2024-04-15 19:15:44 +02:00
Irmen de Jong a7247f5b8b fix boolean expression optimization bug 2024-04-12 21:56:25 +02:00
Irmen de Jong 4d37581694 fix the symbol lookup error lsb(a) when a is in a multi vardecl. 2024-04-11 00:51:08 +02:00
Irmen de Jong 5d7ddebcad fix bool to uword cast in 6502 codegen 2024-04-11 00:34:53 +02:00
Irmen de Jong 53df0eb707 cleanups 2024-04-10 22:04:03 +02:00
Irmen de Jong 8babad9c7c sphinx config 2024-04-10 20:04:09 +02:00
Irmen de Jong 8db7aa07bd added (autogenerated) symbol skeleton files to the docs 2024-04-10 19:58:15 +02:00
Irmen de Jong 42f4b06ac8 added options -bytes2float and -float2bytes to be able to do float conversions from the command line 2024-04-09 23:59:54 +02:00
Irmen de Jong f4b50368ba fix grammar: if_xx with else part 2024-04-09 22:35:30 +02:00
Irmen de Jong db80417bd7 fix a problem with const fold optimization in if expressions, and IR compilation of that 2024-04-09 22:09:29 +02:00
Irmen de Jong 7a6f2ecc8c add symboldumps to doc makefile 2024-04-09 19:53:36 +02:00
Irmen de Jong f5d556a7f9 added missing options to doc 2024-04-09 19:30:04 +02:00
Irmen de Jong 2aae46d632 added -dumpsymbols option to print a dump of all the variables and subroutine signatures 2024-04-09 19:19:13 +02:00
Irmen de Jong 19ebc6d6b3 better error message for ambiguous multi-var initialization in vardecl 2024-04-08 22:36:00 +02:00
Irmen de Jong f88c29e083 convert github doc links into permalinks 2024-04-08 22:12:28 +02:00
Irmen de Jong 6ed9899dc7 smarter desugaring of ubyte x,y 2024-04-07 23:36:46 +02:00
Irmen de Jong 9de7698a5c verafx.mult() and muls() now return both words of the 32 bits result. 2024-04-07 22:41:21 +02:00
Irmen de Jong 112d2d6058 cx16 sprites module: the palette_offset parameter now takes values 0-15 (instead of 0-255) to be more consistent with docs and vera behavior 2024-04-07 21:49:03 +02:00
Irmen de Jong ddb8346711 added txt.cls() as a shorter alternative to clear_screen().
cx16: added new character encodings, and routines in textio to enable the character sets for them.
cx16: added txt.chrout_lit() and txt.print_lit() to always print the literal characters and never as control codes
2024-04-07 19:32:44 +02:00
Irmen de Jong 8dd3faf395 clarification 2024-04-06 14:31:39 +02:00
Irmen de Jong 35f3e8708b doc and tweak subexpression extraction a tiny bit 2024-04-06 14:01:06 +02:00
Irmen de Jong cfe3fcc9e7 fix symbol table issue 2024-04-06 12:53:33 +02:00
Irmen de Jong 66a6659a6e cbm.STOP2() and cbm.GETIN2() convenience routines 2024-04-06 02:16:21 +02:00
Irmen de Jong 88ae3daa42 Merge branch 'refs/heads/master' into multi-assign
# Conflicts:
#	examples/test.p8
2024-04-06 00:14:41 +02:00
Irmen de Jong 08b8fe01ab added missing cmp #0 after func()==0
cx16: diskio.fastmode() now returns success boolean
2024-04-06 00:04:54 +02:00
Irmen de Jong 731132d4b3 check number of result values in return statements 2024-04-05 02:13:31 +02:00
Irmen de Jong 98acff802f better checking for number of return values
assignment optimization if return register already is the same as the assignment target
2024-04-04 23:47:33 +02:00
Irmen de Jong 5f11f485a2 fix compiler error 2024-04-04 02:00:55 +02:00
Irmen de Jong 34f3169dda tweak library routines for multiple return values.
cbm:
MEMTOP changed (now also returns nr of banks in A)
STOP2 removed (just use STOP)
RDTIM_safe() added                  TEST IRQ ENABLE
RDTIM16 changed (internally)        TEST IRQ ENABLE

cx16:
screen_mode changed (now also returns width and height in X,Y)
kbdbuf_peek2 removed (just use kbdbuf_peek)
joystick_get changed (presence now returned as bool in Y)
joystick_get2 removed (just use joystick_get)
mouse_pos changed (now properly returns x and y position in R0 and R1)
set_led_brightness changed into set_led_state, with only a boolean on/off argument. There is no variable brightness.

sys.set_leds_brightness() removed. Use cx16.set_led_brightness().
2024-04-04 01:39:19 +02:00
Irmen de Jong a3ef8f814b Merge branch 'master' into multi-assign
# Conflicts:
#	examples/test.p8
2024-04-03 01:13:27 +02:00
Irmen de Jong 385dd6fc23 todos 2024-04-03 01:12:45 +02:00
Irmen de Jong 9af4168ae2 cx16: added diskio.fastmode() to select the fast serial disk mode for the SD card 2024-04-02 22:17:51 +02:00
Irmen de Jong a5e0e31b74 clarify order of multi-assign 2024-04-02 01:47:46 +02:00
Irmen de Jong b385dc8c26 add cx16 extapi ROM call, call numbers and shims. (new in Rom R47) 2024-04-02 01:45:10 +02:00
Irmen de Jong 92c012b55a fix IR peephole optimization 2024-04-02 00:28:28 +02:00
Irmen de Jong 641f6c05d8 allow 'void' as dummy assign target in multi-assignment statements 2024-03-31 23:43:26 +02:00
Irmen de Jong 788f6b44a6 antlr grammar now understands underscores in identifier names 2024-03-31 00:31:10 +01:00
Irmen de Jong 63a4525f06 remove hacks from floats.parse now that kernal R47 is out 2024-03-30 22:29:13 +01:00
Irmen de Jong 3e34a3ef72 allow multi-assign to skip any status register result 2024-03-29 23:10:08 +01:00
Irmen de Jong 0c5e8ca199 Merge branch 'master' into multi-assign 2024-03-29 11:51:42 +01:00
Irmen de Jong ff23fb0086 take ignore_unused option into account for warnings about removing unused blocks themselves as well 2024-03-29 00:16:18 +01:00
Irmen de Jong 56f41d5e34 docs about multi-assign 2024-03-28 23:24:14 +01:00
Irmen de Jong 4700a239b9 Merge branch 'master' into multi-assign
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2024-03-28 01:06:43 +01:00
Irmen de Jong bd5abfb969 add IR peephole optimization to remove redundant store 2024-03-28 01:06:05 +01:00
Irmen de Jong b93fa75377 consolidate cbm textio routines 2024-03-28 00:39:58 +01:00
Irmen de Jong 681ce9c60c fix void warning 2024-03-27 23:05:41 +01:00
Irmen de Jong dd0f0fe415 conv.str_ub and partners are now much shorter routines than before 2024-03-27 22:34:44 +01:00
Irmen de Jong 119040fc50 also add diskio.status_code() in other comp targets 2024-03-27 20:05:39 +01:00
adiee5 551e5688da Add diskio.status_code() function (#130) 2024-03-27 19:42:47 +01:00
Irmen de Jong 56c1035581 Merge branch 'master' into multi-assign
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2024-03-26 22:09:16 +01:00
Irmen de Jong ba1e907c79 fix divmod; out args are written to and should be potential constants 2024-03-26 22:04:44 +01:00
Irmen de Jong 2a3a27c56d bmx library: set bpp header field correctly on save 2024-03-26 22:01:10 +01:00
markjreed 647af34f5b fix: tweak divmod() doc (#131)
* fix: adjust naming on divmod parameters to match standard mathematical terminology; clarify description

* fix: wording

* fix: wording
2024-03-26 22:00:55 +01:00
Irmen de Jong 993be6394e unit tests multi-assigns 2024-03-25 23:20:03 +01:00
Irmen de Jong 9a27505315 6502 codegen for multi-assigns 2024-03-25 22:17:31 +01:00
Irmen de Jong 2e37f5dee3 IR: support for multi-returnvalue function calls (asmsubs)
note: the VM can't execute these though as it has no CPU hardware registers
2024-03-23 00:30:17 +01:00
Irmen de Jong 03e486c082 multi assign 2024-03-22 21:51:25 +01:00
Irmen de Jong edc83305a4 allow multiple targets in AssignTarget 2024-03-22 21:51:08 +01:00
Irmen de Jong 66e7c51064 IR: fix some things related to asmsubs 2024-03-22 21:49:01 +01:00
Irmen de Jong 60244aaf16 64tass version... 2024-03-21 21:40:18 +01:00
Irmen de Jong 443391c700 another way to hash? 2024-03-21 21:30:34 +01:00
Irmen de Jong 47dbafacd4 correct version 2024-03-21 20:15:54 +01:00
Irmen de Jong 5b6811d073 not separate 2024-03-21 20:07:44 +01:00
Irmen de Jong 7516116bb7 last attempt for hash build step 2024-03-21 20:04:07 +01:00
Irmen de Jong e6014ea4dd version 10.3.1 2024-03-21 19:32:16 +01:00
Irmen de Jong 362abfe284 ci 2024-03-20 22:46:18 +01:00
Irmen de Jong ad4880997a no operand swap on logical expressions with shortcircuit evaluation (and,or are no longer associative!) 2024-03-20 22:34:48 +01:00
Irmen de Jong 592becc126 allow %breakpoint also in if/else blocks and other anonymous scopes 2024-03-19 23:31:21 +01:00
Irmen de Jong c38765301e gfx2 screenmode tweak 2024-03-19 01:21:56 +01:00
Irmen de Jong 5f27426f59 only on release event 2024-03-17 23:35:53 +01:00
Irmen de Jong d924f8bff8 another attempt to get the hashes working 2024-03-17 23:31:04 +01:00
Irmen de Jong d14c61b160 added string.findstr(). string.find() returns 255 if not found.
also fix string assignment bug for sub args.
2024-03-17 23:18:33 +01:00
308 changed files with 20980 additions and 5732 deletions

No files matched your search

+8 -14
View File
@@ -18,30 +18,24 @@ jobs:
git clone --depth=1 https://github.com/irmen/64tass
cd 64tass
make -j4
sudo make install
sudo make install
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: 11
distribution: adopt
distribution: temurin
- name: Build and test with Gradle
run: ./gradlew build shadowJar --no-daemon
run: |
./gradlew build shadowJar --no-daemon
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v4
with:
name: prog8-compiler-jar-zipped
path: compiler/build/libs/*-all.jar
path: |
compiler/build/libs/*-all.jar
compiler/build/libs/hash.txt
- uses: MCJack123/ghaction-generate-release-hashes@v4
if: "github.event_name == 'release'"
with:
hash-type: sha256
file-name: hashes.txt
- uses: actions/upload-artifact@v4
if: "github.event_name == 'release'"
with:
name: Artifact Hashes
path: hashes.txt
+1
View File
@@ -1,5 +1,6 @@
.idea/workspace.xml
.idea/discord.xml
.idea/developer-tools.xml
build/
dist/
output/
+2 -2
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
<option name="version" value="1.9.24" />
</component>
</project>
</project>
+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:1.9.22" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
</SOURCES>
</library>
</component>
+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.13.1">
<properties maven-id="org.antlr:antlr4:4.13.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
+10 -13
View File
@@ -1,21 +1,18 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+23
View File
@@ -0,0 +1,23 @@
<component name="libraryTable">
<library name="io.kotest.framework.datatest" type="repository">
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+18 -21
View File
@@ -1,30 +1,30 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
@@ -32,11 +32,8 @@
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+12 -4
View File
@@ -1,12 +1,20 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.20/kotlin-result-jvm-1.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+1 -1
View File
@@ -22,7 +22,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+2 -1
View File
@@ -12,9 +12,10 @@
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
</project>
</project>
+13 -18
View File
@@ -29,6 +29,8 @@ How to get it/build it
- Or, if you want/need a bleeding edge development version, you can:
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
Note that if you are not using *gradle* to build it, you might have to perform some manual
tasks once to make it compile fully. These are explained in the linked instructions.
- Alternatively, you can also install the compiler as a package on some linux distros:
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
@@ -52,26 +54,23 @@ What does Prog8 provide?
------------------------
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects.
- modularity, symbol scoping, subroutines
- programs run very fast because it's compiled to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- floating point math is supported on certain targets
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- no need for forward declarations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
*Rapid edit-compile-run-debug cycle:*
@@ -119,14 +118,13 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
bool[256] sieve
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
sys.memset(sieve, 256, false) ; clear the sieve
sys.memset(sieve, 256, 0) ; clear the sieve
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@@ -162,9 +160,6 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
}
when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
+12
View File
@@ -0,0 +1,12 @@
.PHONY: all clean emu
all: benchmark.prg
clean:
rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z
emu: benchmark.prg
x16emu -run -prg $< -warp
benchmark.prg: benchmark.p8 b_3d.p8 b_adpcm.p8 b_circles.p8 b_life.p8 b_mandelbrot.p8 b_maze.p8 b_queens.p8 b_textelite.p8
p8compile $< -target cx16
+109
View File
@@ -0,0 +1,109 @@
%import textio
%import math
rotate3d {
const ubyte WIDTH = 40
const ubyte HEIGHT = 30
sub benchmark(uword max_time) -> uword {
uword anglex
uword angley
uword anglez
uword frames
txt.nl()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
matrix_math.rotate_vertices(msb(anglex), msb(angley), msb(anglez))
draw_edges() ; doesn't really draw anything in the benchmark, but does do the screen calculations
anglex+=500
angley+=215
anglez+=453
frames++
}
return frames
}
sub draw_edges() {
; plot the points of the 3d cube
; first the points on the back, then the points on the front (painter algorithm)
ubyte @zp i
word @zp rz
word @zp persp
byte @shared sx
byte @shared sy
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz >= 10 {
persp = 600 + rz/64
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
}
}
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz < 10 {
persp = 600 + rz/64
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
}
}
txt.chrout('.')
}
}
matrix_math {
; vertices
word[] @split xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
word[] @split ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
word[] @split zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
; storage for rotated coordinates
word[len(xcoor)] @split rotatedx
word[len(ycoor)] @split rotatedy
word[len(zcoor)] @split rotatedz
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
; rotate around origin (0,0,0)
; set up the 3d rotation matrix values
word wcosa = math.cos8(ax)
word wsina = math.sin8(ax)
word wcosb = math.cos8(ay)
word wsinb = math.sin8(ay)
word wcosc = math.cos8(az)
word wsinc = math.sin8(az)
word wcosa_sinb = wcosa*wsinb / 128
word wsina_sinb = wsina*wsinb / 128
word Axx = wcosa*wcosb / 128
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
word Ayx = wsina*wcosb / 128
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
word Azx = -wsinb
word Azy = wcosb*wsinc / 128
word Azz = wcosb*wcosc / 128
ubyte @zp i
for i in 0 to len(xcoor)-1 {
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
}
}
}
+89
View File
@@ -0,0 +1,89 @@
adpcm {
sub decode_benchmark(uword max_time) -> uword {
uword num_blocks
txt.nl()
cbm.SETTIM(0,0,0)
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
repeat 252/2 {
unroll 2 {
ubyte @zp nibble = @(nibbles_ptr)
adpcm.decode_nibble(nibble & 15) ; first word (note: upper nibble needs to be zero!)
adpcm.decode_nibble(nibble>>4) ; second word (note: upper nibble is zero, after the shifts.)
nibbles_ptr++
}
}
num_blocks++
txt.chrout('.')
}
return num_blocks
}
; IMA ADPCM decoder. Supports mono and stereo streams.
ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]
uword[] @split t_step = [
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411,
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
32767]
uword @requirezp predict ; decoded 16 bit pcm sample for first channel.
ubyte @requirezp index
uword @requirezp pstep
sub init(uword startPredict, ubyte startIndex) {
; initialize first decoding channel.
predict = startPredict
index = startIndex
pstep = t_step[index]
}
sub decode_nibble(ubyte @zp nibble) {
; Decoder for a single nibble for the first channel. (value of 'nibble' needs to be strictly 0-15 !)
; This is the hotspot of the decoder algorithm!
; Note that the generated assembly from this is pretty efficient,
; rewriting it by hand in asm seems to improve it only ~10%.
cx16.r0s = 0 ; difference
if nibble & %0100 !=0
cx16.r0s += pstep
pstep >>= 1
if nibble & %0010 !=0
cx16.r0s += pstep
pstep >>= 1
if nibble & %0001 !=0
cx16.r0s += pstep
pstep >>= 1
cx16.r0s += pstep
if nibble & %1000 !=0
predict -= cx16.r0
else
predict += cx16.r0
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
; if predicted > 32767:
; predicted = 32767
; elif predicted < -32767:
; predicted = - 32767
index += t_index[nibble]
if_neg
index = 0
else if index >= len(t_step)-1
index = len(t_step)-1
pstep = t_step[index]
}
}
+111
View File
@@ -0,0 +1,111 @@
%import gfx2
%import math
circles {
const ubyte MAX_NUM_CIRCLES = 80
const ubyte GROWTH_RATE = 4
uword[MAX_NUM_CIRCLES] @split circle_x
uword[MAX_NUM_CIRCLES] @split circle_y
ubyte[MAX_NUM_CIRCLES] circle_radius
ubyte color
uword total_num_circles
sub draw(bool use_kernal, uword max_time) -> uword {
if use_kernal
void cx16.set_screen_mode(128)
else
gfx2.screen_mode(1)
math.rndseed(12345,6789)
cbm.SETTIM(0,0,0)
total_num_circles = 0
color = 16
while cbm.RDTIM16()<max_time {
if use_kernal {
cx16.GRAPH_set_colors(0,0,0)
cx16.GRAPH_clear()
}
else
gfx2.clear_screen(0)
total_num_circles += draw_circles(use_kernal, max_time)
}
if use_kernal
void cx16.set_screen_mode(3)
else {
gfx2.screen_mode(0)
}
return total_num_circles
}
sub draw_circles(bool use_kernal, uword max_time) -> uword {
uword @zp x
uword @zp y
ubyte @zp radius
ubyte num_circles
while num_circles<MAX_NUM_CIRCLES and cbm.RDTIM16()<max_time {
x = math.rndw() % 320
y = math.rndw() % 240
radius = GROWTH_RATE
if not_colliding() {
while not_edge() and not_colliding() {
radius += GROWTH_RATE
}
radius -= GROWTH_RATE
if radius>0 {
color++
if color==0
color=16
if use_kernal {
cx16.GRAPH_set_colors(color, 255-color, 0)
cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true)
}
else
gfx2.disc(x, y as ubyte, radius, color)
circle_x[num_circles] = x
circle_y[num_circles] = y
circle_radius[num_circles] = radius
num_circles++
}
}
}
return num_circles
sub not_colliding() -> bool {
if num_circles==0
return true
ubyte @zp c
for c in 0 to num_circles-1 {
if distance(c) < (radius as uword) + circle_radius[c]
return false
}
return true
}
sub distance(ubyte cix) -> uword {
word dx = x as word - circle_x[cix]
word dy = y as word - circle_y[cix]
uword sqx = dx*dx as uword
uword sqy = dy*dy as uword
return sqrt(sqx + sqy)
}
sub not_edge() -> bool {
if x as word - radius < 0
return false
if x + radius >= 320
return false
if y as word - radius < 0
return false
if y + radius >= 240
return false
return true
}
}
}
+123
View File
@@ -0,0 +1,123 @@
; conway's game of life.
%import math
%import textio
life {
const ubyte WIDTH = 40
const ubyte HEIGHT = 30
const uword STRIDE = $0002+WIDTH
uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
uword @requirezp active_world = world1
sub benchmark(uword max_time) -> uword {
txt.clear_screen()
sys.memset(world1, (WIDTH+2)*(HEIGHT+2), 0)
sys.memset(world2, (WIDTH+2)*(HEIGHT+2), 0)
set_start_gen()
uword gen
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
next_gen()
gen++
}
return gen
}
sub set_start_gen() {
; some way to set a custom start generation:
; str start_gen = " " +
; " " +
; " " +
; " ** " +
; " * * " +
; " * " +
; " * * " +
; " ****** " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " "
;
; for y in 0 to 15 {
; for x in 0 to 15 {
; if start_gen[y*16 + x]=='*'
; active_world[offset + x] = 1
; }
; offset += STRIDE
; }
; randomize whole world
math.rndseed(12345,9999)
uword offset = STRIDE+1
ubyte x
ubyte y
for y in 0 to HEIGHT-1 {
for x in 0 to WIDTH-1 {
active_world[offset+x] = math.rnd() & 1
}
offset += STRIDE
}
}
sub next_gen() {
const ubyte DXOFFSET = 0
const ubyte DYOFFSET = 0
ubyte[2] cell_chars = [sc:' ', sc:'●']
uword @requirezp new_world = world1
if active_world == world1
new_world = world2
; To avoid re-calculating word index lookups into the new- and active world arrays,
; we calculate the required pointer values upfront.
; Inside the loop we can use ptr+x just fine (results in efficient LDA (ptr),Y instruction because x is a byte type),
; and for each row we simply add the stride to the pointer.
; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces
; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop.
uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
ubyte x
ubyte y
for y in DYOFFSET to HEIGHT+DYOFFSET-1 {
cx16.vaddr_autoincr(1, $b000 + 256*y, 0, 2) ; allows us to use simple Vera data byte assigns later instead of setchr() calls
for x in DXOFFSET to WIDTH+DXOFFSET-1 {
; count the living neighbors
ubyte cell = @(active_world_ptr + x)
uword @requirezp ptr = active_world_ptr + x - STRIDE - 1
ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) +
@(ptr+STRIDE) + cell + @(ptr+STRIDE+2) +
@(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2)
; apply game of life rules
if neighbors==3
cell=1
else if neighbors!=4
cell=0
@(new_world_ptr + x) = cell
; draw new cell
; txt.setchr(x,y,cell_chars[cell])
cx16.VERA_DATA0 = cell_chars[cell]
}
active_world_ptr += STRIDE
new_world_ptr += STRIDE
}
active_world = new_world
}
}
+54
View File
@@ -0,0 +1,54 @@
%import textio
%import floats
mandelbrot {
const ubyte width = 39
const ubyte height = 29
const ubyte max_iter = 15
sub calc(uword max_time) -> uword {
uword num_pixels
ubyte pixelx
ubyte pixely
txt.home()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16() < max_time {
for pixely in 0 to height-1 {
float yy = (pixely as float)/0.40/height - 1.3
for pixelx in 0 to width-1 {
float xx = (pixelx as float)/0.32/width - 2.2
float xsquared = 0.0
float ysquared = 0.0
float x = 0.0
float y = 0.0
ubyte iter = 0
while iter<max_iter and xsquared+ysquared<4.0 {
y = x*y*2.0 + yy
x = xsquared - ysquared + xx
xsquared = x*x
ysquared = y*y
iter++
}
txt.color2(1, max_iter-iter)
txt.spc()
num_pixels++
if cbm.RDTIM16()>=max_time
goto finished
}
txt.nl()
}
txt.clear_screen()
}
finished:
txt.color2(1, 6)
return num_pixels
}
}
+343
View File
@@ -0,0 +1,343 @@
%import textio
%import math
; Even though prog8 only has support for extremely limited recursion,
; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure.
; This program shows a depth-first maze generation algorithm (1 possible path from start to finish),
; and a depth-first maze solver algorithm, both using a stack to store the path taken.
; Note: this program can be compiled for multiple target systems.
maze {
uword score
sub bench(uword max_time) -> uword {
txt.nl()
score=0
math.rndseed(2345,44332)
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
maze.initialize()
maze.drawStartFinish()
if maze.generate(max_time) {
maze.openpassages()
maze.drawStartFinish()
if maze.solve(max_time) {
maze.drawStartFinish()
} else break
} else break
}
return score
}
const uword screenwidth = 40
const uword screenheight = 30
const ubyte numCellsHoriz = (screenwidth-1) / 2
const ubyte numCellsVert = (screenheight-1) / 2
; maze start and finish cells
const ubyte startCx = 0
const ubyte startCy = 0
const ubyte finishCx = numCellsHoriz-1
const ubyte finishCy = numCellsVert-1
; cell properties
const ubyte STONE = 128
const ubyte WALKED = 64
const ubyte BACKTRACKED = 32
const ubyte UP = 1
const ubyte RIGHT = 2
const ubyte DOWN = 4
const ubyte LEFT = 8
const ubyte WALLCOLOR = 12
const ubyte EMPTYCOLOR = 0
; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore.
uword cells = memory("cells", numCellsHoriz*numCellsVert, 0)
ubyte[256] cx_stack
ubyte[256] cy_stack
ubyte stackptr
ubyte[4] directionflags = [LEFT,RIGHT,UP,DOWN]
sub generate(uword max_time) -> bool {
ubyte cx = startCx
ubyte cy = startCy
stackptr = 0
@(celladdr(cx,cy)) &= ~STONE
drawCell(cx, cy)
uword cells_to_carve = numCellsHoriz * numCellsVert - 1
while cbm.RDTIM16()<max_time {
carve_restart_after_repath:
ubyte direction = choose_uncarved_direction()
if direction==0 {
;backtrack
stackptr--
if stackptr==255 {
; stack empty.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
}
return true
}
cx = cx_stack[stackptr]
cy = cy_stack[stackptr]
} else {
cx_stack[stackptr] = cx
cy_stack[stackptr] = cy
stackptr++
if stackptr==0 {
; stack overflow, we can't track our path any longer.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
}
return true
}
@(celladdr(cx,cy)) |= direction
when direction {
UP -> {
cy--
@(celladdr(cx,cy)) |= DOWN
}
RIGHT -> {
cx++
@(celladdr(cx,cy)) |= LEFT
score++
}
DOWN -> {
cy++
@(celladdr(cx,cy)) |= UP
}
LEFT -> {
cx--
@(celladdr(cx,cy)) |= RIGHT
}
}
@(celladdr(cx,cy)) &= ~STONE
cells_to_carve--
drawCell(cx, cy)
}
}
return false
sub repath() -> bool {
; repath: try to find a new start cell with possible directions.
; we limit our number of searches so that the algorith doesn't get stuck
; for too long on bad rng... just accept a few unused cells in that case.
repeat 255 {
do {
cx = math.rnd() % numCellsHoriz
cy = math.rnd() % numCellsVert
} until @(celladdr(cx, cy)) & STONE ==0
if available_uncarved()!=0
return true
}
return false
}
sub available_uncarved() -> ubyte {
ubyte candidates = 0
if cx>0 and @(celladdr(cx-1, cy)) & STONE !=0
candidates |= LEFT
if cx<numCellsHoriz-1 and @(celladdr(cx+1, cy)) & STONE !=0
candidates |= RIGHT
if cy>0 and @(celladdr(cx, cy-1)) & STONE !=0
candidates |= UP
if cy<numCellsVert-1 and @(celladdr(cx, cy+1)) & STONE !=0
candidates |= DOWN
return candidates
}
sub choose_uncarved_direction() -> ubyte {
ubyte candidates = available_uncarved()
if candidates==0
return 0
repeat {
ubyte choice = candidates & directionflags[math.rnd() & 3]
if choice!=0
return choice
}
}
}
sub openpassages() {
; open just a few extra passages, so that multiple routes are possible in theory.
ubyte numpassages
ubyte cx
ubyte cy
do {
do {
cx = math.rnd() % (numCellsHoriz-2) + 1
cy = math.rnd() % (numCellsVert-2) + 1
} until @(celladdr(cx, cy)) & STONE ==0
ubyte direction = directionflags[math.rnd() & 3]
if @(celladdr(cx, cy)) & direction == 0 {
when direction {
LEFT -> {
if @(celladdr(cx-1,cy)) & STONE == 0 {
@(celladdr(cx,cy)) |= LEFT
drawCell(cx,cy)
numpassages++
}
}
RIGHT -> {
if @(celladdr(cx+1,cy)) & STONE == 0 {
@(celladdr(cx,cy)) |= RIGHT
drawCell(cx,cy)
numpassages++
}
}
UP -> {
if @(celladdr(cx,cy-1)) & STONE == 0 {
@(celladdr(cx,cy)) |= UP
drawCell(cx,cy)
numpassages++
}
}
DOWN -> {
if @(celladdr(cx,cy+1)) & STONE == 0 {
@(celladdr(cx,cy)) |= DOWN
drawCell(cx,cy)
numpassages++
}
}
}
}
} until numpassages==10
}
sub solve(uword max_time) -> bool {
ubyte cx = startCx
ubyte cy = startCy
const uword max_path_length = 1024
; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :(
uword pathstack = memory("pathstack", max_path_length, 0)
uword pathstackptr = 0
@(celladdr(cx,cy)) |= WALKED
; txt.setcc(cx*2+1, cy*2+1, 81, 1)
while cbm.RDTIM16()<max_time {
solve_loop:
if cx==finishCx and cy==finishCy {
;txt.home()
txt.print("found! path length: ")
txt.print_uw(pathstackptr)
txt.nl()
return true
}
ubyte cell = @(celladdr(cx,cy))
if cell & UP!=0 and @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = UP
;txt.setcc(cx*2+1, cy*2, 81, 3)
cy--
}
else if cell & DOWN !=0 and @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = DOWN
;txt.setcc(cx*2+1, cy*2+2, 81, 3)
cy++
}
else if cell & LEFT !=0 and @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = LEFT
;txt.setcc(cx*2, cy*2+1, 81, 3)
cx--
}
else if cell & RIGHT !=0 and @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = RIGHT
;txt.setcc(cx*2+2, cy*2+1, 81, 3)
cx++
}
else {
; dead end, pop stack
pathstackptr--
if pathstackptr==65535 {
txt.print("no solution?!\n")
return true
}
@(celladdr(cx,cy)) |= BACKTRACKED
;txt.setcc(cx*2+1, cy*2+1, 81, 2)
when @(pathstack + pathstackptr) {
UP -> {
;txt.setcc(cx*2+1, cy*2+2, 81, 9)
cy++
}
DOWN -> {
;txt.setcc(cx*2+1, cy*2, 81, 9)
cy--
}
LEFT -> {
;txt.setcc(cx*2+2, cy*2+1, 81, 9)
cx++
}
RIGHT -> {
;txt.setcc(cx*2, cy*2+1, 81, 9)
cx--
score++
}
}
goto solve_loop
}
pathstackptr++
if pathstackptr==max_path_length {
txt.print("stack overflow, path too long\n")
return true
}
@(celladdr(cx,cy)) |= WALKED
;txt.setcc(cx*2+1, cy*2+1, 81, 1)
}
return false
}
sub celladdr(ubyte cx, ubyte cy) -> uword {
return cells+(numCellsHoriz as uword)*cy+cx
}
sub drawCell(ubyte cx, ubyte cy) {
return
; ubyte x = cx * 2 + 1
; ubyte y = cy * 2 + 1
; ubyte doors = @(celladdr(cx,cy))
; if doors & UP !=0
; txt.setcc(x, y-1, ' ', EMPTYCOLOR)
; if doors & RIGHT !=0
; txt.setcc(x+1, y, ' ', EMPTYCOLOR)
; if doors & DOWN !=0
; txt.setcc(x, y+1, ' ', EMPTYCOLOR)
; if doors & LEFT !=0
; txt.setcc(x-1, y, ' ', EMPTYCOLOR)
; if doors & STONE !=0
; txt.setcc(x, y, 160, WALLCOLOR)
; else
; txt.setcc(x, y, 32, EMPTYCOLOR)
;
; if doors & WALKED !=0
; txt.setcc(x, y, 81, 1)
; if doors & BACKTRACKED !=0
; txt.setcc(x, y, 81, 2)
}
sub initialize() {
sys.memset(cells, numCellsHoriz*numCellsVert, STONE)
; txt.fill_screen(160, WALLCOLOR)
drawStartFinish()
}
sub drawStartFinish() {
; txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5)
; txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13)
}
}
+63
View File
@@ -0,0 +1,63 @@
%import textio
; Recursive N-Queens solver.
; The problem is: find all possible ways to place 8 Queen chess pieces on a chess board, so that none of them attacks any other.
; (this program prints all solutions without taking mirroring and flipping the chess board into account)
; Note: this program can be compiled for multiple target systems.
queens {
const ubyte NUMQUEENS=8
ubyte[NUMQUEENS] board
sub could_place(ubyte row, ubyte col) -> bool {
if row==0
return true
ubyte i
for i in 0 to row-1 {
if board[i]==col or board[i]-i==col-row or board[i]+i==col+row
return false
}
return true
}
uword solution_count
uword maximum_duration
sub place_queen(ubyte row) -> bool {
if row == NUMQUEENS {
solution_count++
txt.chrout('.')
return cbm.RDTIM16()<maximum_duration
}
bool continue_running=true
ubyte col
for col in 0 to NUMQUEENS-1 {
if could_place(row, col) {
board[row] = col
; we need to save the local variables row and col.
sys.push(row)
sys.push(col)
continue_running = place_queen(row + 1)
; restore the local variables after the recursive call.
col = sys.pop()
row = sys.pop()
board[row] = 0
if not continue_running
break
}
}
return continue_running
}
sub bench(uword max_time) -> uword {
solution_count = 0
maximum_duration = max_time
txt.nl()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16() < maximum_duration {
void place_queen(0)
}
return solution_count
}
}
+990
View File
@@ -0,0 +1,990 @@
%import textio
%import conv
%import string
%import string
textelite {
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
uword num_commands
sub bench(uword max_time) -> uword {
num_commands = 0
txt.lowercase()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
reinit()
run_commands(max_time)
}
return num_commands
}
sub reinit() {
;txt.clear_screen()
;txt.print("\n --- TextElite v1.3 ---\n")
txt.print("\nnew game\n")
elite_planet.set_seed(0, 0)
elite_galaxy.travel_to(1, numforLave)
elite_market.init(0) ; Lave's market is seeded with 0
elite_ship.init()
elite_planet.display(false, 0)
input_index = 0
}
sub run_commands(uword max_time) {
while cbm.RDTIM16() < max_time {
str input = "????????"
;txt.print("\nCash: ")
;elite_util.print_10s(elite_ship.cash)
;txt.print("\nCommand (?=help): ")
ubyte num_chars = next_input(input)
;txt.nl()
if num_chars!=0 {
when input[0] {
'q' -> {
bool has_error = false
if elite_galaxy.number != 2 {
txt.print("\nERROR: galaxy is not 2: ")
txt.print_ub(elite_galaxy.number)
txt.nl()
has_error=true
}
if elite_planet.number != 164 {
txt.print("\nERROR: planet is not 164: ")
txt.print_ub(elite_planet.number)
txt.nl()
has_error=true
}
if elite_planet.x != 116 {
txt.print("\nERROR: planet.x is not 116: ")
txt.print_ub(elite_planet.x)
txt.nl()
has_error=true
}
if elite_planet.y != 201 {
txt.print("\nERROR: planet.y is not 201: ")
txt.print_ub(elite_planet.y)
txt.nl()
has_error=true
}
if "ribeen" != elite_planet.name {
txt.print("\nERROR: planet.name is not 'ribeen': ")
txt.print(elite_planet.name)
txt.nl()
has_error=true
}
if elite_ship.cash != 1212 {
txt.print("\nERROR: cash is not 1212: ")
txt.print_uw(elite_ship.cash)
txt.nl()
has_error=true
}
if elite_ship.fuel != 50 {
txt.print("\nERROR: fuel is not 50:")
txt.print_ub(elite_ship.fuel)
txt.nl()
has_error=true
}
if elite_ship.cargohold[0] != 3 {
txt.print("\nERROR: food is not 3:")
txt.print_ub(elite_ship.cargohold[0])
txt.nl()
has_error=true
}
if elite_ship.cargohold[1] != 0 {
txt.print("\nERROR: textiles is not 0:")
txt.print_ub(elite_ship.cargohold[1])
txt.nl()
has_error=true
}
if has_error
sys.exit(1)
return
}
'b' -> elite_trader.do_buy()
's' -> elite_trader.do_sell()
'f' -> elite_trader.do_fuel()
'j' -> elite_trader.do_jump()
't' -> elite_trader.do_teleport()
'g' -> elite_trader.do_next_galaxy()
'i' -> elite_trader.do_info()
'm' -> {
if input[1]=='a' and input[2]=='p'
elite_trader.do_map()
else
elite_trader.do_show_market()
}
'l' -> elite_trader.do_local()
'c' -> elite_trader.do_cash()
'h' -> elite_trader.do_hold()
}
num_commands++
}
}
}
str[] inputs = [
"i",
"diso",
"i",
"lave",
"m",
"b",
"food",
"15",
"map",
"g",
"map",
"l",
"j",
"zao",
"s",
"food",
"12",
"tele",
"quti",
"tele",
"aro",
"i",
"diso",
"i",
"lave",
"i",
"zao",
"galhyp",
"fuel",
"20",
"j",
"rib",
"i",
"rib",
"i",
"tiri",
"q",
0
]
ubyte input_index
sub next_input(str buffer) -> ubyte {
input_index++
return string.copy(inputs[input_index], buffer)
}
}
elite_trader {
str input = "??????????"
ubyte num_chars
sub do_jump() {
;txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
;txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = elite_ship.fuel
elite_ship.fuel = 255
jump_to_system()
elite_ship.fuel = fuel
}
sub jump_to_system() {
void textelite.next_input(input)
ubyte current_planet = elite_planet.number
ubyte x = elite_planet.x
ubyte y = elite_planet.y
if elite_galaxy.search_closest_planet(input) {
ubyte distance = elite_planet.distance(x, y)
if distance <= elite_ship.fuel {
elite_galaxy.init_market_for_planet()
elite_ship.fuel -= distance
;txt.print("\n\nHyperspace jump! Arrived at:\n")
elite_planet.display(true,0 )
return
}
;txt.print("\nInsufficient fuel\n")
} else {
;txt.print(" Not found!\n")
}
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
}
sub do_buy() {
;txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void textelite.next_input(commodity)
ubyte ci = elite_market.match(commodity)
if ci & 128 !=0 {
txt.print("Unknown\n")
} else {
;txt.print("\nHow much? ")
void textelite.next_input(input)
ubyte amount = conv.str2ubyte(input)
if elite_market.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = elite_market.current_price[ci] * amount
;txt.print(" Total price: ")
;elite_util.print_10s(price)
if price > elite_ship.cash {
txt.print(" Not enough cash!\n")
} else {
elite_ship.cash -= price
elite_ship.cargohold[ci] += amount
elite_market.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
;txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void textelite.next_input(commodity)
ubyte ci = elite_market.match(commodity)
if ci & 128 !=0 {
txt.print("Unknown\n")
} else {
;txt.print("\nHow much? ")
void textelite.next_input(input)
ubyte amount = conv.str2ubyte(input)
if elite_ship.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = elite_market.current_price[ci] * amount
;txt.print(" Total price: ")
;elite_util.print_10s(price)
elite_ship.cash += price
elite_ship.cargohold[ci] -= amount
elite_market.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
;txt.print("\nBuy fuel. Amount? ")
void textelite.next_input(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = elite_ship.Max_fuel - elite_ship.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * elite_ship.Fuel_cost
if price > elite_ship.cash {
txt.print("Not enough cash!\n")
} else {
elite_ship.cash -= price
elite_ship.fuel += buy_fuel
}
}
sub do_cash() {
;txt.print("\nCheat! Set cash amount: ")
void textelite.next_input(input)
elite_ship.cash = conv.str2uword(input)
}
sub do_hold() {
;txt.print("\nCheat! Set cargohold size: ")
void textelite.next_input(input)
elite_ship.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy() {
txt.print("\n>>>>> Galaxy Hyperjump!\n")
elite_galaxy.travel_to(elite_galaxy.number+1, elite_planet.number)
elite_planet.display(false, 0)
}
sub do_info() {
;txt.print("\nSystem name (empty=current): ")
num_chars = textelite.next_input(input)
if num_chars!=0 {
ubyte current_planet = elite_planet.number
ubyte x = elite_planet.x
ubyte y = elite_planet.y
if elite_galaxy.search_closest_planet(input) {
ubyte distance = elite_planet.distance(x, y)
elite_planet.display(false, distance)
} else {
;txt.print(" Not found!")
}
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
} else {
elite_planet.display(false, 0)
}
}
sub do_local() {
elite_galaxy.local_area()
}
sub do_map() {
;txt.print("\n(l)ocal or (g)alaxy starmap? ")
num_chars = textelite.next_input(input)
if num_chars!=0 {
elite_galaxy.starmap(input[0]=='l')
}
}
sub do_show_market() {
elite_market.display()
;txt.print("\nFuel: ")
;elite_util.print_10s(elite_ship.fuel)
;txt.print(" Cargohold space: ")
;txt.print_ub(elite_ship.cargo_free())
;txt.print("t\n")
}
}
elite_ship {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel
uword cash
ubyte[17] cargohold
sub init() {
sys.memset(cargohold, len(cargohold), 0)
fuel = Max_fuel
cash = 1000
}
}
elite_market {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity
uword[17] current_price
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all commands are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = elite_planet.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80 !=0
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
return
; ubyte ci
; txt.nl()
; elite_planet.print_name_uppercase()
; txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
; for ci in 0 to len(names)-1 {
; elite_util.print_right(13, names[ci])
; txt.print(" ")
; elite_util.print_10s(current_price[ci])
; txt.column(24)
; txt.print_ub(current_quantity[ci])
; txt.chrout(' ')
; when units[ci] {
; 0 -> txt.chrout('t')
; 1 -> txt.print("kg")
; 2 -> txt.chrout('g')
; }
; txt.column(32)
; txt.print_ub(elite_ship.cargohold[ci])
; txt.nl()
; }
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if elite_util.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
elite_galaxy {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxynum) {
number = 1
elite_planet.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxynum-1 {
nextgalaxy()
}
}
sub nextgalaxy() {
textelite.num_commands++
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxynum, ubyte system) {
init(galaxynum)
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet()
textelite.num_commands++
}
elite_planet.name = make_current_planet_name()
init_market_for_planet()
}
sub init_market_for_planet() {
elite_market.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet(uword nameptr) -> bool {
textelite.num_commands++
ubyte x = elite_planet.x
ubyte y = elite_planet.y
ubyte current_planet_num = elite_planet.number
init(number)
bool found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet()
elite_planet.name = make_current_planet_name()
if elite_util.prefix_matches(nameptr, elite_planet.name) {
ubyte distance = elite_planet.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet_num)
return found
}
sub local_area() {
ubyte current_planet = elite_planet.number
ubyte px = elite_planet.x
ubyte py = elite_planet.y
ubyte pn = 0
init(number)
; txt.print("\nGalaxy #")
; txt.print_ub(number)
; txt.print(" - systems in vicinity:\n")
do {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= elite_ship.Max_fuel {
; if distance <= elite_ship.fuel
; txt.chrout('*')
; else
; txt.chrout('-')
; txt.spc()
elite_planet.name = make_current_planet_name()
elite_planet.display(true, distance)
}
pn++
} until pn==0
travel_to(number, current_planet)
}
sub starmap(bool local) {
ubyte current_planet = elite_planet.number
ubyte px = elite_planet.x
ubyte py = elite_planet.y
str current_name = " " ; 8 max
ubyte pn = 0
current_name = elite_planet.name
init(number)
; txt.clear_screen()
; txt.print("Galaxy #")
; txt.print_ub(number)
; if local
; txt.print(" - local systems")
; else
; txt.print(" - galaxy")
; txt.print(" starmap:\n")
ubyte max_distance = 255
if local
max_distance = elite_ship.Max_fuel
ubyte home_sx
ubyte home_sy
ubyte home_distance
do {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= max_distance {
elite_planet.name = make_current_planet_name()
elite_planet.name[0] = string.upperchar(elite_planet.name[0])
uword tx = elite_planet.x
uword ty = elite_planet.y
if local {
tx = tx + 24 - px
ty = ty + 24 - py
}
ubyte sx = display_scale_x(tx)
ubyte sy = display_scale_y(ty)
ubyte char = '*'
if elite_planet.number==current_planet
char = '%'
if local {
print_planet_details(elite_planet.name, sx, sy, distance)
} else if elite_planet.number==current_planet {
home_distance = distance
home_sx = sx
home_sy = sy
}
; txt.setchr(2+sx, 2+sy, char)
}
pn++
} until pn==0
if not local
print_planet_details(current_name, home_sx, home_sy, home_distance)
; if local
; txt.plot(0, display_scale_y(64) + 4)
; else
; txt.plot(0, display_scale_y(256) + 4 as ubyte)
travel_to(number, current_planet)
sub print_planet_details(str name, ubyte screenx, ubyte screeny, ubyte d) {
return
; txt.plot(2+screenx-2, 2+screeny+1)
; txt.print(name)
; if d!=0 {
; txt.plot(2+screenx-2, 2+screeny+2)
; elite_util.print_10s(d)
; txt.print(" LY")
; }
}
sub display_scale_x(uword x) -> ubyte {
if local
return x/2 as ubyte
return x/8 as ubyte
}
sub display_scale_y(uword y) -> ubyte {
if local
return y/4 as ubyte
return y/16 as ubyte
}
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
bool longname
sub generate_next_planet() {
determine_planet_properties()
longname = lsb(seed[0]) & 64 !=0
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet_properties() {
; create the planet's characteristics
elite_planet.number++
elite_planet.x = msb(seed[1])
elite_planet.y = msb(seed[0])
elite_planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
elite_planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if elite_planet.govtype <= 1
elite_planet.economy = (elite_planet.economy | 2)
elite_planet.techlevel = (msb(seed[1]) & 3) + (elite_planet.economy ^ 7)
elite_planet.techlevel += elite_planet.govtype >> 1
if elite_planet.govtype & 1 !=0
elite_planet.techlevel++
elite_planet.population = 4 * elite_planet.techlevel + elite_planet.economy
elite_planet.population += elite_planet.govtype + 1
elite_planet.productivity = ((elite_planet.economy ^ 7) + 3) * (elite_planet.govtype + 4)
elite_planet.productivity *= elite_planet.population * 8
ubyte seed2_msb = msb(seed[2])
elite_planet.radius = mkword((seed2_msb & 15) + 11, elite_planet.x)
elite_planet.species_is_alien = lsb(seed[2]) & 128 !=0 ; bit 7 of w2_lo
if elite_planet.species_is_alien {
elite_planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
elite_planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
elite_planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
elite_planet.species_kind = (elite_planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
elite_planet.goatsoup_seed[0] = lsb(seed[1])
elite_planet.goatsoup_seed[1] = msb(seed[1])
elite_planet.goatsoup_seed[2] = lsb(seed[2])
elite_planet.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
xh <<= 1 ; make sure carry flag is not used on first shift!
rol(xl)
return mkword(xh, xl)
}
}
elite_planet {
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet", "world", "place", "little planet", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] @shared wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
bool species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str randname = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte xx = goatsoup_rnd_number() & $3e
if pairs0[xx] != '.' {
randname[nx] = pairs0[xx]
nx++
}
xx++
if pairs0[xx] != '.' {
randname[nx] = pairs0[xx]
nx++
}
}
randname[nx] = 0
randname[0] = string.upperchar(randname[0])
return randname
}
sub goatsoup_rnd_number() -> ubyte {
ubyte xx = goatsoup_rnd[0] * 2
uword a = xx as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = xx
xx = goatsoup_rnd[1]
ubyte ac = xx + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = xx
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet_result
reset_rnd()
recursive_soup()
return planet_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = ((rnr >= $33) as ubyte) + ((rnr >= $66) as ubyte) + ((rnr >= $99) as ubyte) + ((rnr >= $CC) as ubyte)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = string.upperchar(name[0])
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = string.upperchar(name[0])
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc in ['e', 'o', 0]
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(bool compressed, ubyte distance) {
txt.print(soup())
txt.nl()
}
sub getword(ubyte listnum, ubyte wordidx) -> uword {
uword list = wordlists[listnum-$81]
return peekw(list + wordidx*2)
}
}
elite_util {
sub prefix_matches(uword prefixptr, uword stringptr) -> bool {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
if string.lowerchar(pc)!=string.lowerchar(sc)
return false
prefixptr++
stringptr++
}
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/compiled" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+112
View File
@@ -0,0 +1,112 @@
; This benchmark program is meant to check for regressions in the
; Prog8 compiler's code-generator (performance wise).
;
; As the X16 computer is a more or less fixed system, it's not very useful
; to benchmark the computer itself with.
%import textio
%import b_adpcm
%import b_circles
%import b_3d
%import b_life
%import b_mandelbrot
%import b_queens
%import b_textelite
%import b_maze
%zeropage basicsafe
%option no_sysinit
main {
str[20] benchmark_names
uword[20] benchmark_score
sub start() {
ubyte benchmark_number
void cx16.set_screen_mode(3)
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n")
sys.wait(60)
benchmark_number = 0
announce_benchmark("maze solver")
benchmark_score[benchmark_number] = maze.bench(300)
benchmark_number++
announce_benchmark("n-queens")
benchmark_score[benchmark_number] = queens.bench(300)
benchmark_number++
announce_benchmark("mandelbrot (floating point)")
benchmark_score[benchmark_number] = mandelbrot.calc(400)
benchmark_number++
announce_benchmark("game of life")
benchmark_score[benchmark_number] = life.benchmark(300)
benchmark_number++
announce_benchmark("3d model rotation")
benchmark_score[benchmark_number] = rotate3d.benchmark(300)
benchmark_number++
announce_benchmark("adpcm audio decoding")
benchmark_score[benchmark_number] = adpcm.decode_benchmark(300)
benchmark_number++
announce_benchmark("circles with gfx2")
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
void cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
void cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
txt.clear_screen()
txt.plot(4, 6)
txt.print(benchmark_names[benchmark_number])
txt.nl()
sys.wait(60)
}
}
}
+3 -4
View File
@@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -26,7 +25,7 @@ compileTestKotlin {
dependencies {
// should have no dependencies to other modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
}
sourceSets {
+2 -3
View File
@@ -82,8 +82,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
override fun lookup(scopedName: String) = flat[scopedName]
fun getLength(name: String): Int? {
val node = flat[name]
return when(node) {
return when(val node = flat[name]) {
is StMemVar -> node.length
is StMemorySlab -> node.size.toInt()
is StStaticVariable -> node.length
@@ -223,7 +222,7 @@ class StMemVar(name: String,
init{
require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL)
if(dt in ArrayDatatypes || dt == DataType.STR)
require(length!=null) { "memory mapped array or string must have known length" }
requireNotNull(length)
}
}
+9 -8
View File
@@ -3,7 +3,8 @@ package prog8.code
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import java.util.*
import kotlin.collections.ArrayDeque
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
fun make(): SymbolTable {
@@ -13,8 +14,8 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
}
val scopestack = Stack<StNode>()
scopestack.push(st)
val scopestack = ArrayDeque<StNode>()
scopestack.add(st)
program.children.forEach {
addToSt(it, scopestack)
}
@@ -35,7 +36,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
return st
}
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
private fun addToSt(node: PtNode, scope: ArrayDeque<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
@@ -104,7 +105,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
}
null
}
@@ -112,14 +113,14 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
}
if(stNode!=null) {
scope.peek().add(stNode)
scope.push(stNode)
scope.last().add(stNode)
scope.add(stNode)
}
node.children.forEach {
addToSt(it, scope)
}
if(stNode!=null)
scope.pop()
scope.removeLast()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
+32 -28
View File
@@ -1,7 +1,7 @@
package prog8.code.ast
import prog8.code.core.*
import java.util.*
import java.util.Objects
import kotlin.math.abs
import kotlin.math.truncate
@@ -42,9 +42,20 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
else
other.left isSameAs left && other.right isSameAs right
}
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtContainmentCheck -> {
if(other !is PtContainmentCheck || other.type != type || !(other.needle isSameAs needle))
false
else {
if(haystackHeapVar!=null)
other.haystackHeapVar!=null && other.haystackHeapVar!! isSameAs haystackHeapVar!!
else if(haystackValues!=null)
other.haystackValues!=null && other.haystackValues!! isSameAs haystackValues!!
else
false
}
}
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtIrRegister -> other is PtIrRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number
is PtBool -> other is PtBool && other.value==value
@@ -87,7 +98,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtContainmentCheck -> false
is PtFunctionCall -> false
is PtIdentifier -> true
is PtMachineRegister -> true
is PtIrRegister -> true
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
is PtBool -> true
is PtNumber -> true
@@ -139,13 +150,9 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
val variable: PtIdentifier
get() {
require((children[0] as? PtIdentifier)?.type in ArrayDatatypes+DataType.STR) // TODO remove
return children[0] as PtIdentifier
}
get() = children[0] as PtIdentifier
val index: PtExpression
get() = children[1] as PtExpression
val splitWords: Boolean
get() = variable.type in SplitWordArrayTypes
@@ -199,10 +206,17 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val element: PtExpression
val needle: PtExpression
get() = children[0] as PtExpression
val iterable: PtIdentifier
get() = children[1] as PtIdentifier
val haystackHeapVar: PtIdentifier?
get() = children[1] as? PtIdentifier
val haystackValues: PtArray?
get() = children[1] as? PtArray
companion object {
val MAX_SIZE_FOR_INLINE_CHECKS_BYTE = 5
val MAX_SIZE_FOR_INLINE_CHECKS_WORD = 4
}
}
@@ -210,11 +224,6 @@ class PtFunctionCall(val name: String,
val void: Boolean,
type: DataType,
position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<PtExpression>
get() = children.map { it as PtExpression }
}
@@ -236,12 +245,6 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOOL, position) {
companion object {
fun fromNumber(number: Number, position: Position): PtBool =
PtBool(if(number==0.0) false else true, position)
}
override fun hashCode(): Int = Objects.hash(type, value)
override fun equals(other: Any?): Boolean {
@@ -276,12 +279,12 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtNumber)
return false
return if(other==null || other !is PtNumber)
false
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
number==other.number
else
return type==other.type && number==other.number
type==other.type && number==other.number
}
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
@@ -311,6 +314,7 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal == toVal -> fromVal .. toVal
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
@@ -354,7 +358,7 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null
+23 -14
View File
@@ -10,7 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> "<target>"
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint"
@@ -21,7 +21,16 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
"&"
}
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
is PtArray -> {
val valuelist = node.children.map {
when (it) {
is PtNumber -> it.number.toString()
is PtIdentifier -> it.name
else -> "?"
}
}.joinToString(", ")
"array len=${node.children.size} ${type(node.type)} [ $valuelist ]"
}
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
@@ -34,7 +43,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
str + node.name + "()"
}
is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtIrRegister -> "IRREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()"
is PtNumber -> {
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
@@ -63,18 +72,18 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
"???"
}
is PtAsmSub -> {
val params = node.parameters.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
}.joinToString(", ")
val params = node.parameters.joinToString(", ") {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
}
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returns.isEmpty()) "" else {
"-> ${node.returns.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second} @${register ?: statusflag}"}
.joinToString(", ")
"-> ${node.returns.joinToString(", ") {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second} @${register ?: statusflag}"
}
}"
}
val str = if (node.inline) "inline " else ""
@@ -108,7 +117,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
}
is PtSub -> {
val params = node.parameters.map { "${it.type} ${it.name}" }.joinToString(", ")
val params = node.parameters.joinToString(", ") { "${it.type} ${it.name}" }
var str = "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"
+14 -4
View File
@@ -5,6 +5,7 @@ import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
val scopedName: String
}
class PtAsmSub(
@@ -41,9 +42,18 @@ class PtSubroutineParameter(name: String, val type: DataType, position: Position
sealed interface IPtAssignment {
val children: MutableList<PtNode>
val target: PtAssignTarget
get() = children[0] as PtAssignTarget
get() {
if(children.size==2)
return children[0] as PtAssignTarget
else if(children.size<2)
throw AssemblyError("incomplete node")
else
throw AssemblyError("no singular target")
}
val value: PtExpression
get() = children[1] as PtExpression
get() = children.last() as PtExpression
val multiTarget: Boolean
get() = children.size>2
}
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
@@ -51,7 +61,7 @@ class PtAssignment(position: Position) : PtNode(position), IPtAssignment
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
class PtAssignTarget(position: Position) : PtNode(position) {
class PtAssignTarget(val void: Boolean, position: Position) : PtNode(position) {
val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier
val array: PtArrayIndexer?
@@ -69,7 +79,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
}
}
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
infix fun isSameAs(expression: PtExpression): Boolean = !void && expression.isSameAs(this)
}
@@ -76,14 +76,14 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
"prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
@@ -96,11 +96,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"divmod" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
@@ -130,7 +128,12 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"rrestore" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar2" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("argA", arrayOf(DataType.UBYTE)), FParam("argX", arrayOf(DataType.UBYTE)), FParam("argY", arrayOf(DataType.UBYTE)), FParam("argC", arrayOf(DataType.BOOL))), DataType.UWORD),
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"divmod", "divmod__ubyte", "divmod__uword"
)
@@ -20,13 +20,13 @@ class CompilationOptions(val output: OutputType,
var asmListfile: Boolean = false,
var includeSourcelines: Boolean = false,
var dumpVariables: Boolean = false,
var dumpSymbols: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var strictBool: Boolean = true,
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
@@ -1,6 +1,11 @@
package prog8.code.core
import kotlin.math.abs
import kotlin.math.pow
val powersOfTwoFloat = (0..16).map { (2.0).pow(it) }.toTypedArray()
val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray()
val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray()
fun Number.toHex(): String {
// 0..15 -> "0".."15"
+2 -2
View File
@@ -78,7 +78,7 @@ enum class RegisterOrPair {
R8, R9, R10, R11, R12, R13, R14, R15;
companion object {
val names by lazy { values().map { it.toString()} }
val names by lazy { entries.map { it.toString()} }
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
@@ -104,7 +104,7 @@ enum class Statusflag {
Pn; // don't use
companion object {
val names by lazy { values().map { it.toString()} }
val names by lazy { entries.map { it.toString()} }
}
}
@@ -26,7 +26,9 @@ interface IMachineDefinition {
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun convertFloatToBytes(num: Double): List<UByte>
fun convertBytesToFloat(bytes: List<UByte>): Double
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isIOAddress(address: UInt): Boolean
}
@@ -5,7 +5,11 @@ enum class Encoding(val prefix: String) {
PETSCII("petscii"), // c64/c128/cx16
SCREENCODES("sc"), // c64/c128/cx16
ATASCII("atascii"), // atari
ISO("iso") // cx16
ISO("iso"), // cx16 (iso-8859-15)
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
CP437("cp437"), // cx16 (ibm pc, codepage 437)
KATAKANA("kata") // cx16 (katakana)
}
interface IStringEncoding {
+1 -1
View File
@@ -1,6 +1,6 @@
package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are no longer associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~")
+108 -93
View File
@@ -1,13 +1,18 @@
package prog8.code.optimize
import prog8.code.StRomSub
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
if (!options.optimize)
return
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
while (errors.noErrors() &&
(optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling
}
}
@@ -22,109 +27,119 @@ private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
}
private var tempVarCounter = 0
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
val result = if(expr is PtBinaryExpression)
expr.type !in ByteDatatypes ||
!expr.left.isSimple() ||
!expr.right.isSimple() ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
true
else
!expr.isSimple()
return result
}
// for each Binaryexpression, recurse to find a common subexpression pair therein.
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
var changes = 0
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtBinaryExpression) {
val subExpressions = mutableListOf<PtExpression>()
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
if(node is PtAssignment) {
val value = node.value
val functionName = when(value) {
is PtBuiltinFunctionCall -> value.name
is PtFunctionCall -> value.name
else -> null
}
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
if(functionName!=null) {
val stNode = st.lookup(functionName)
if (stNode is StRomSub) {
require(node.children.size==stNode.returns.size+1) {
"number of targets must match return values"
}
node.children.zip(stNode.returns).withIndex().forEach { (index, xx) ->
val target = xx.first as PtAssignTarget
val returnedRegister = xx.second.register.registerOrPair
if(returnedRegister!=null && !target.void && target.identifier!=null) {
if(isSame(target.identifier!!, xx.second.type, returnedRegister)) {
// output register is already identical to target register, so it can become void
val voidTarget = PtAssignTarget(true, target.position)
node.children[index] = voidTarget
voidTarget.parent = node
changes++
}
}
}
}
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
val index = node.parent.children.indexOf(node)
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
value.children.forEach { voidCall.add(it) }
node.parent.children[index] = voidCall
voidCall.parent = node.parent
changes++
}
}
}
true
}
return changes
}
outer@for (first in subExpressions) {
for (second in subExpressions) {
if (first!==second && first isSameAs second) {
commons[node] = first to second
break@outer // do only 1 replacement at a time per binaryexpression
private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int {
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
return 0 // the special bittest optimization is not yet valid for the IR
var changes = 0
var recurse = true
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtIfElse) {
val condition = node.condition as? PtBinaryExpression
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
if(condition.right.asConstInteger()==0) {
val and = condition.left as? PtBinaryExpression
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
val variable = and.left as? PtIdentifier
val bitmask = and.right.asConstInteger()
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
val setOrNot = if(condition.operator=="!=") "set" else "notset"
val index = node.parent.children.indexOf(node)
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
bittestCall.add(variable)
if(bitmask==128)
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
else
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
val ifElse = PtIfElse(node.position)
ifElse.add(bittestCall)
ifElse.add(node.ifScope)
if(node.hasElse())
ifElse.add(node.elseScope)
node.parent.children[index] = ifElse
ifElse.parent = node.parent
changes++
recurse = false
}
}
}
}
false
} else true
}
// replace common subexpressions by a temp variable that is assigned only once.
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
commons.forEach { binexpr, (occurrence1, occurrence2) ->
val (stmtContainer, stmt) = findContainingStatements(binexpr)
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
val containerScopedName = findScopeName(stmtContainer)
tempVarCounter++
val tempvarName = "prog8_subexprvar_$tempVarCounter"
// TODO: some tempvars could be reused, if they are from different lines
val datatype = occurrence1.type
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
occurrence1.parent.children[occurrence1idx] = singleReplacement1
singleReplacement1.parent = occurrence1.parent
occurrence2.parent.children[occurrence2idx] = singleReplacement2
singleReplacement2.parent = occurrence2.parent
val tempassign = PtAssignment(binexpr.position).also { assign ->
assign.add(PtAssignTarget(binexpr.position).also { tgt->
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
})
assign.add(occurrence1)
occurrence1.parent = assign
}
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
tempassign.parent = stmtContainer
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position)
stmtContainer.add(0, tempvar)
tempvar.parent = stmtContainer
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
recurse
}
return commons.size
return changes
}
internal fun findScopeName(node: PtNode): String {
var parent=node
while(parent !is PtNamedNode)
parent = parent.parent
return parent.scopedName
}
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
var parent = node.parent
var child = node
while(true) {
if(parent is IPtStatementContainer) {
return parent to child
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
if(returnedRegister in Cx16VirtualRegisters) {
val regname = returnedRegister.name.lowercase()
val identifierRegName = identifier.name.substringAfterLast('.')
/*
cx16.r? UWORD
cx16.r?s WORD
cx16.r?L UBYTE
cx16.r?H UBYTE
cx16.r?sL BYTE
cx16.r?sH BYTE
*/
if(identifier.type in ByteDatatypes && type in ByteDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "L", "sL") // note: not the -H (msb) variants!
}
}
else if(identifier.type in WordDatatypes && type in WordDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "s")
}
}
child=parent
parent=parent.parent
}
return false // there are no identifiers directly corresponding to cpu registers
}
@@ -19,3 +19,23 @@ class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Cb
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
}
}
val CompilationTargets = listOf(
C64Target.NAME,
C128Target.NAME,
Cx16Target.NAME,
PETTarget.NAME,
AtariTarget.NAME,
VMTarget.NAME
)
fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
+9 -3
View File
@@ -4,9 +4,7 @@ import com.github.michaelbull.result.fold
import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
import prog8.code.target.cbm.AtasciiEncoding
import prog8.code.target.cbm.IsoEncoding
import prog8.code.target.cbm.PetsciiEncoding
import prog8.code.target.encodings.*
object Encoder: IStringEncoding {
@@ -18,6 +16,10 @@ object Encoder: IStringEncoding {
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return coded.fold(
@@ -31,6 +33,10 @@ object Encoder: IStringEncoding {
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return decoded.fold(
@@ -22,13 +22,8 @@ class AtariMachineDefinition: IMachineDefinition {
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.output == OutputType.XEX)
listOf("syslib")
else
emptyList()
}
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
@@ -25,11 +25,15 @@ class C128MachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
@@ -26,11 +26,15 @@ class C64MachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
@@ -24,11 +24,16 @@ class CX16MachineDefinition: IMachineDefinition {
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
@@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
@@ -0,0 +1,69 @@
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object Cp437Encoding {
val charset: Charset = Charset.forName("IBM437")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\u00a0' -> 255u
'☺' -> 1u
'☻' -> 2u
'♥' -> 3u
'♦' -> 4u
'♣' -> 5u
'♠' -> 6u
'•' -> 7u
'◘' -> 8u
'○' -> 9u
'◙' -> 10u
'♂' -> 11u
'♀' -> 12u
'♪' -> 13u
'♫' -> 14u
'☼' -> 15u
'►' -> 16u
'◄' -> 17u
'↕' -> 18u
'‼' -> 19u
'¶' -> 20u
'§' -> 21u
'▬' -> 22u
'↨' -> 23u
'↑' -> 24u
'↓' -> 25u
'→' -> 26u
'←' -> 27u
'∟' -> 28u
'↔' -> 29u
'▲' -> 30u
'▼' -> 31u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> charset.encode(chr.toString())[0].toUByte()
}
}
Ok(mapped)
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}
@@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
@@ -6,8 +6,8 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object IsoEncoding {
val charset: Charset = Charset.forName("ISO-8859-15")
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
@@ -35,3 +35,8 @@ object IsoEncoding {
}
}
}
object IsoEncoding: IsoEncodingBase("ISO-8859-15")
object IsoCyrillicEncoding: IsoEncodingBase("ISO-8859-5")
object IsoEasternEncoding: IsoEncodingBase("ISO-8859-16")
@@ -0,0 +1,122 @@
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object JapaneseCharacterConverter {
// adapted from https://github.com/raminduw/Japanese-Character-Converter
private val ZENKAKU_KATAKANA = charArrayOf(
'ァ', 'ア', 'ィ', 'イ', 'ゥ',
'ウ', 'ェ', 'エ', 'ォ', 'オ', 'カ', 'ガ', 'キ', 'ギ', 'ク', 'グ', 'ケ', 'ゲ',
'コ', 'ゴ', 'サ', 'ザ', 'シ', 'ジ', 'ス', 'ズ', 'セ', 'ゼ', 'ソ', 'ゾ', 'タ',
'ダ', 'チ', 'ヂ', 'ッ', 'ツ', 'ヅ', 'テ', 'デ', 'ト', 'ド', 'ナ', 'ニ', 'ヌ',
'ネ', 'ノ', 'ハ', 'バ', 'パ', 'ヒ', 'ビ', 'ピ', 'フ', 'ブ', 'プ', 'ヘ', 'ベ',
'ペ', 'ホ', 'ボ', 'ポ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ャ', 'ヤ', 'ュ', 'ユ',
'ョ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ヮ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
'ヴ', 'ヵ', 'ヶ'
)
private val HANKAKU_HIRAGANA = charArrayOf(
'ぁ', 'あ', 'ぃ', 'い', 'ぅ', 'う', 'ぇ', 'え',
'ぉ', 'お', 'か', 'が', 'き', 'ぎ', 'く', 'ぐ',
'け', 'げ', 'こ', 'ご', 'さ', 'ざ', 'し', 'じ',
'す', 'ず', 'せ', 'ぜ', 'そ', 'ぞ', 'た', 'だ',
'ち', 'ぢ', 'っ', 'つ', 'づ', 'て', 'で', 'と',
'ど', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ば',
'ぱ', 'ひ', 'び', 'ぴ', 'ふ', 'ぶ', 'ぷ', 'へ',
'べ', 'ぺ', 'ほ', 'ぼ', 'ぽ', 'ま', 'み', 'む',
'め', 'も', 'ゃ', 'や', 'ゅ', 'ゆ', 'ょ', 'よ',
'ら', 'り', 'る', 'れ', 'ろ', 'ゎ', 'わ', 'ゐ',
'ゑ', 'を', 'ん', 'ゔ', 'ゕ', 'ゖ'
)
private val HANKAKU_KATAKANA = arrayOf(
"ァ", "ア", "ィ", "イ", "ゥ",
"ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ",
"ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ",
"ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド",
"ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ",
"ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ",
"モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ",
"ワ", "イ", "エ", "ヲ", "ン", "ヴ", "カ", "ケ"
)
private val ZENKAKU_KATAKANA_FIRST_CHAR_CODE = ZENKAKU_KATAKANA.first().code
private val HANKAKU_HIRAGANA_FIRST_CHAR_CODE = HANKAKU_HIRAGANA.first().code
private fun zenkakuKatakanaToHankakuKatakana(c: Char): String = if (c in ZENKAKU_KATAKANA) HANKAKU_KATAKANA[c.code - ZENKAKU_KATAKANA_FIRST_CHAR_CODE] else c.toString()
private fun hankakuKatakanaToZenkakuKatakana(c: Char): Char = if (c in HANKAKU_HIRAGANA) ZENKAKU_KATAKANA[c.code - HANKAKU_HIRAGANA_FIRST_CHAR_CODE] else c
fun zenkakuKatakanaToHankakuKatakana(s: String): String = buildString {
for (element in s) {
val converted = hankakuKatakanaToZenkakuKatakana(element)
val convertedChar = zenkakuKatakanaToHankakuKatakana(converted)
append(convertedChar)
}
}
}
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
'♥' -> 0xe3u
'♦' -> 0xe4u
'♣' -> 0xe5u
'♠' -> 0xe6u
'大' -> 0xeau
'中' -> 0xebu
'小' -> 0xecu
'百' -> 0xedu
'千' -> 0xeeu
'万' -> 0xefu
'♪' -> 0xf0u
'土' -> 0xf1u
'金' -> 0xf2u
'木' -> 0xf3u
'水' -> 0xf4u
'火' -> 0xf5u
'月' -> 0xf6u
'日' -> 0xf7u
'時' -> 0xf8u
'分' -> 0xf9u
'秒' -> 0xfau
'年' -> 0xfbu
'円' -> 0xfcu
'人' -> 0xfdu
'生' -> 0xfeu
'〒' -> 0xffu
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> charset.encode(chr.toString())[0].toUByte()
}
}
Ok(mapped)
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}
@@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
@@ -25,11 +25,15 @@ class PETMachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
@@ -30,8 +30,24 @@ class VirtualMachineDefinition: IMachineDefinition {
return parts.joinToString(", ")
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib")
override fun convertFloatToBytes(num: Double): List<UByte> {
val bits = num.toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() }
return parts
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==8) { "need 8 bytes" }
val b0 = bytes[0].toLong() shl (8*7)
val b1 = bytes[1].toLong() shl (8*6)
val b2 = bytes[2].toLong() shl (8*5)
val b3 = bytes[3].toLong() shl (8*4)
val b4 = bytes[4].toLong() shl (8*3)
val b5 = bytes[5].toLong() shl (8*2)
val b6 = bytes[6].toLong() shl (8*1)
val b7 = bytes[7].toLong() shl (8*0)
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
+5 -5
View File
@@ -5,9 +5,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -26,9 +25,10 @@ dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1
View File
@@ -14,5 +14,6 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
@@ -9,7 +9,7 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.assignment.*
import java.util.*
import kotlin.collections.ArrayDeque
import kotlin.io.path.Path
import kotlin.io.path.writeLines
@@ -66,7 +66,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
if(node.type in SplitWordArrayTypes && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName)!!
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
@@ -100,12 +100,11 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
nodesToPrefix.forEach { (parent, index) ->
val node = parent.children[index]
when(node) {
when(val node = parent.children[index]) {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
is PtVariable -> parent.children[index] = node.prefix(st)
is PtVariable -> parent.children[index] = node.prefix(parent, st)
else -> throw AssemblyError("weird node to prefix $node")
}
}
@@ -136,7 +135,7 @@ private fun prefixScopedName(name: String, type: Char): String {
return prefixed.joinToString(".")
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
name = prefixScopedName(name, 'v')
if(value==null)
return this
@@ -164,7 +163,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
PtVariable(name, type, zeropage, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, newValue, arraySize, position)
result.parent = parent
result
}
else this
}
@@ -233,7 +234,7 @@ class AsmGen6502Internal (
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, options, anyExprGen, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen, errors)
@@ -247,7 +248,7 @@ class AsmGen6502Internal (
if(errors.noErrors()) {
val output = options.outputDir.resolve("${program.name}.asm")
val asmLines = assembly.asSequence().flatMapTo(mutableListOf()) { it.split('\n') }
val asmLines = assembly.flatMapTo(mutableListOf()) { it.split('\n') }
if(options.compTarget.name==Cx16Target.NAME) {
scanInvalid65816instructions(asmLines)
if(!errors.noErrors()) {
@@ -375,7 +376,7 @@ class AsmGen6502Internal (
val name = asmSymbolName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length + 1)
else
@@ -386,14 +387,14 @@ class AsmGen6502Internal (
val name = asmVariableName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length+1)
else
name
}
fun asmVariableName(st: StNode, scope: PtSub?): String {
fun asmVariableName(st: StNode, scope: IPtSubroutine?): String {
val name = asmVariableName(st.scopedName)
if(scope==null)
return name
@@ -587,7 +588,10 @@ class AsmGen6502Internal (
is PtInlineAssembly -> translate(stmt)
is PtBuiltinFunctionCall -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
is PtFunctionCall -> functioncallAsmGen.translateFunctionCallStatement(stmt)
is PtAssignment -> assignmentAsmGen.translate(stmt)
is PtAssignment -> {
if(stmt.multiTarget) assignmentAsmGen.translateMultiAssign(stmt)
else assignmentAsmGen.translate(stmt)
}
is PtAugmentedAssign -> assignmentAsmGen.translate(stmt)
is PtJump -> {
val (asmLabel, indirect) = getJumpTarget(stmt)
@@ -770,7 +774,7 @@ class AsmGen6502Internal (
private fun translate(stmt: PtRepeatLoop) {
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopEndLabels.add(endLabel)
when (stmt.count) {
is PtNumber -> {
@@ -814,20 +818,18 @@ class AsmGen6502Internal (
}
}
loopEndLabels.pop()
loopEndLabels.removeLast()
}
private fun repeatWordCount(count: Int, stmt: PtRepeatLoop) {
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
// the iny + double dec is microoptimization of the 16 bit loop
val loopcount = if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$count
lda #<$count
beq +
iny
+ sta $counterVar
ldy #>$loopcount
lda #<$loopcount
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.statements)
@@ -880,8 +882,8 @@ $repeatLabel""")
}
private fun repeatCountInY(stmt: PtRepeatLoop, endLabel: String) {
// note: Y must just have been loaded with the (variable) number of loops to be performed!
val repeatLabel = makeLabel("repeat")
out(" cpy #0")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
out(" beq $endLabel | sty $counterVar")
@@ -1317,91 +1319,6 @@ $repeatLabel""")
}
}
internal fun popCpuStack(asmsub: PtAsmSub, parameter: PtSubroutineParameter, reg: RegisterOrStatusflag) {
val shouldKeepA = asmsub.parameters.any { it.first.registerOrPair==RegisterOrPair.AX || it.first.registerOrPair==RegisterOrPair.AY}
if(reg.statusflag!=null) {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG")
out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
out(" lda P8ZP_SCRATCH_REG")
}
else {
if (parameter.type in ByteDatatypesWithBoolean) {
if (isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> out(" plx")
RegisterOrPair.Y -> out(" ply")
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
out(" pla | tay")
}
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" plx | pla")
RegisterOrPair.AY -> out(" ply | pla")
RegisterOrPair.XY -> out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" pla | tax | pla")
RegisterOrPair.AY -> out(" pla | tay | pla")
RegisterOrPair.XY -> out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
}
internal fun popCpuStack(dt: DataType) {
if (dt in ByteDatatypesWithBoolean) {
out(" pla")
} else if (dt in WordDatatypes) {
if (isTargetCpu(CpuType.CPU65c02))
out(" ply | pla")
else
out(" pla | tay | pla")
} else {
throw AssemblyError("can't pop type $dt")
}
}
internal fun pushCpuStack(dt: DataType, value: PtExpression) {
val signed = value.type.oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypesWithBoolean) {
@@ -50,6 +50,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeTSBtoRegularOr(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
@@ -65,8 +72,6 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
// TODO more assembly peephole optimizations
return numberOfOptimizations
}
@@ -386,6 +391,7 @@ private fun optimizeStoreLoadSame(
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
val third = lines[3].value.trimStart()
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
@@ -395,7 +401,6 @@ private fun optimizeStoreLoadSame(
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
(first.startsWith("ldx ") && second.startsWith("ldx "))
) {
val third = lines[3].value.trimStart()
val attemptRemove =
if(third.isBranch()) {
// a branch instruction follows, we can only remove the load instruction if
@@ -510,34 +515,36 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
val third = lines[2].value
if(!haslabel(second)) {
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
if (!haslabel(second)) {
if ((" lda" in first || "\tlda" in first) && (" cmp #0" in second || "\tcmp #0" in second) ||
(" ldx" in first || "\tldx" in first) && (" cpx #0" in second || "\tcpx #0" in second) ||
(" ldy" in first || "\tldy" in first) && (" cpy #0" in second || "\tcpy #0" in second)
@@ -579,6 +586,58 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
mods += Modification(lines[3].index, true, null)
}
}
fun sameLabel(branchInstr: String, jumpInstr: String, labelInstr: String): Boolean {
if('(' in jumpInstr) return false // indirect jump cannot be replaced
val label = labelInstr.trimEnd().substringBefore(':').substringBefore(' ').substringBefore('\t')
val branchLabel = branchInstr.trimStart().substring(3).trim()
return label==branchLabel
}
// beq Label + jmp Addr + Label -> bne Addr
if((" jmp" in second || "\tjmp " in second) && haslabel(third)) {
if((" beq " in first || "\tbeq " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bne")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bne " in first || "\tbne " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "beq")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bcc " in first || "\tbcc " in first) && sameLabel(first, second, third)){
val branch = second.replace("jmp", "bcs")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bcs " in first || "\tbcs " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bcc")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bpl " in first || "\tbpl " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bmi")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bmi " in first || "\tbmi " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bpl")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvc " in first || "\tbvc " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvs")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvs " in first || "\tbvs " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvc")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
}
}
return mods
}
@@ -616,6 +675,52 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
optimize('a', lines)
optimize('x', lines)
optimize('y', lines)
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
val third = lines[3].value.trimStart()
// phy + ldy + pla -> tya + ldy
// phx + ldx + pla -> txa + ldx
// pha + lda + pla -> nop
if(first=="phy" && second.startsWith("ldy ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " tya"))
}
else if(first=="phx" && second.startsWith("ldx ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " txa"))
}
else if(first=="pha" && second.startsWith("lda ") && third=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return mods
}
private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// Asm peephole: lda var2 / tsb var1 / lda var1 Replace this with this to save 1 cycle: lda var1 / ora var2 / sta var1
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
if(first.startsWith("lda") && second.startsWith("tsb") && third.startsWith("lda")) {
val operand1 = first.substring(3)
val operand2 = second.substring(3)
val operand3 = third.substring(3)
if(operand1!=operand2 && operand2==operand3) {
mods.add(Modification(lines[0].index, false, " lda $operand2"))
mods.add(Modification(lines[1].index, false, " ora $operand1"))
mods.add(Modification(lines[2].index, false, " sta $operand2"))
}
}
}
return mods
}
@@ -28,7 +28,7 @@ internal class AssemblyProgram(
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", // "-Wno-strict-bool", "-Werror",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile"
)
if(options.warnSymbolShadowing)
@@ -39,8 +39,9 @@ internal class AssemblyProgram(
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
if(options.asmListfile) {
command.addAll(listOf("--list=$listFile", "--no-monitor"))
}
val outFile = when (options.output) {
OutputType.PRG -> {
@@ -33,7 +33,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, resultRegister, sscope)
"sgn" -> funcSgn(fcall, resultRegister, sscope)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
"divmod__ubyte" -> funcDivmod(fcall)
@@ -44,8 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
@@ -67,7 +64,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"callfar2" -> funcCallFar2(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
@@ -82,8 +82,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val source = fcall.args[0] as PtIdentifier
val target = fcall.args[1] as PtIdentifier
val sourceSymbol = asmgen.symbolTable.lookup(source.name)
val numElements = when(sourceSymbol) {
val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) {
is StStaticVariable -> sourceSymbol.length!!
is StMemVar -> sourceSymbol.length!!
else -> 0
@@ -268,33 +267,110 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcCall(fcall: PtBuiltinFunctionCall) {
// note: the routine can return a word value (in AY)
val constAddr = fcall.args[0].asConstInteger()
if(constAddr!=null) {
asmgen.out(" jsr ${constAddr.toHex()}")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address
asmgen.out("""
sta (+)+1
sty (+)+2
+ jsr 0 ; modified""")
return
}
// note: the routine can return a word value (in AY)
val identifier = fcall.args[0] as? PtIdentifier
if(identifier!=null) {
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
pha
lda #<((+)-1)
pha
jmp (${asmgen.asmSymbolName(identifier)})
+""")
return
}
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
pha
lda #<((+)-1)
pha
jmp (P8ZP_SCRATCH_W2)
+""")
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
}
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
private fun funcCallFar2(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar2 only works on cx16 target at this time")
fun assignArgs() {
fun assign(value: PtExpression, register: Char) {
when(value) {
is PtBool -> asmgen.out(" ld$register #${value.asInt()}")
is PtNumber -> asmgen.out(" ld$register #${value.number.toInt()}")
is PtIdentifier -> asmgen.out(" ld$register ${asmgen.asmVariableName(value)}")
else -> TODO("callfar2: support non-simple expressions for arguments")
}
}
assign(fcall.args[2], 'a')
assign(fcall.args[3], 'x')
assign(fcall.args[4], 'y')
val carry = fcall.args[5].asConstInteger()
if(carry!=null)
asmgen.out(if(carry==0) " clc" else " sec")
else
TODO("callfar2: support non-const argument values")
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
assignArgs()
asmgen.out("""
jsr cx16.JSRFAR
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
assignArgs()
asmgen.out("""
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
}
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
@@ -404,115 +480,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
val varName = asmgen.asmVariableName(variable)
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_w""")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr floats.func_reverse_f""")
}
in SplitWordArrayTypes -> {
// reverse the lsb and msb arrays both, independently
asmgen.out("""
lda #<${varName}_lsb
ldy #>${varName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b
lda #<${varName}_msb
ldy #>${varName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
else -> throw AssemblyError("weird type")
}
}
private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val varName = asmgen.asmVariableName(variable)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_sort_ub""")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
in SplitWordArrayTypes -> TODO("split words sort")
else -> throw AssemblyError("weird type")
}
}
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
when (what.type) {
@@ -827,8 +794,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
when (dt) {
when (val dt = fcall.args.single().type) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@@ -839,63 +805,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
}
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
val dt = fcall.args.single().type
val array = fcall.args[0] as PtIdentifier
when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A")
}
DataType.ARRAY_F -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr floats.func_${fcall.name}_f_into_A")
}
in SplitWordArrayTypes -> {
val numElements = (asmgen.symbolTable.lookup(array.name) as StStaticVariable).length
when(fcall.name) {
"any" -> {
// any(lsb-array) or any(msb-array)
val arrayName = asmgen.asmVariableName(array)
asmgen.out("""
lda #<${arrayName}_lsb
ldy #>${arrayName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out(" bne +") // shortcircuit
asmgen.out("""
pha
lda #<${arrayName}_msb
ldy #>${arrayName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out("""
sta P8ZP_SCRATCH_REG
pla
ora P8ZP_SCRATCH_REG
+""")
}
"all" -> {
TODO("split words all")
}
else -> throw AssemblyError("weird call")
}
}
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
}
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
@@ -1291,7 +1200,44 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
when(resultRegister) {
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the msb byte out of the word array
if(arg.splitWords) {
val arrayVar = asmgen.asmVariableName(arg.variable)+"_msb"
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" ldy $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" ldx $arrayVar,y")
}
else -> throw AssemblyError("invalid reg")
}
} else {
val arrayVar = asmgen.asmVariableName(arg.variable)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar+1,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" ldy $arrayVar+1,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" ldx $arrayVar+1,y")
}
else -> throw AssemblyError("invalid reg")
}
}
} else when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
@@ -1306,6 +1252,23 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" pla")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya | ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" txa | ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
asmgen.out(" tya | tax | ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
val reg = "cx16.${resultRegister.toString().lowercase()}"
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" sty ${reg}L | lda #0 | sta ${reg}H | lda ${reg}L")
}
else -> throw AssemblyError("invalid reg")
}
}
@@ -1347,7 +1310,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" lda $arrayVar,x")
asmgen.out(" ldy $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@@ -1385,11 +1348,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(arg, resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
asmgen.out(" lda cx16.r0L")
val reg = "cx16.${resultRegister.toString().lowercase()}"
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" sta ${reg}L | ldy #0 | sty ${reg}H | cmp #0")
}
else -> throw AssemblyError("invalid reg")
}
@@ -1398,8 +1359,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
// address goes in P8ZP_SCRATCH_W1, number of elements in A
val symbol = asmgen.symbolTable.lookup(arg.name)
val numElements = when(symbol) {
val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
@@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
@@ -48,25 +48,43 @@ internal class ForLoopsAsmGen(
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
if (stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
bne $loopLabel""")
}
else if (stepsize==1 || stepsize==-1) {
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
}
else
asmgen.out("""
sec
@@ -75,12 +93,13 @@ internal class ForLoopsAsmGen(
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
}
else
asmgen.out(" cmp $varname | bcc $endLabel")
asmgen.out(" sta $modifiedLabel+1")
@@ -100,8 +119,6 @@ $modifiedLabel cmp #0 ; modified
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
@@ -155,58 +172,85 @@ $modifiedLabel cmp #0 ; modified
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
if(stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #1
bne +
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize == 1 || stepsize == -1) {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
asmgen.jmp(loopLabel)
}
stepsize > 0 -> {
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.out(endLabel)
}
else if (stepsize > 0) {
// (u)words, step >= 2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -222,8 +266,8 @@ $modifiedLabel2 lda #0 ; modified
bcc $endLabel
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -239,22 +283,20 @@ $modifiedLabel lda #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else -> {
}
else {
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
// (u)words, step <= -2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
@@ -271,13 +313,12 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -333,10 +374,9 @@ $endLabel""")
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val symbol = asmgen.symbolTable.lookup(ident.name)
val numElements = when(symbol) {
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
@@ -478,17 +518,19 @@ $loopLabel sty $indexVar
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
if(range.last==range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range, iterableDt==DataType.ARRAY_UB)
}
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
if(range.last==range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
@@ -496,7 +538,7 @@ $loopLabel sty $indexVar
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2
@@ -508,7 +550,7 @@ $loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
}
2 -> {
if(range.last==255 || range.last==254) {
@@ -602,13 +644,13 @@ $loopLabel""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
@@ -628,50 +670,50 @@ $endLabel""")
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression, unsigned: Boolean) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
if(!unsigned || range.first<=127) {
asmgen.out("""
dec $varname
bpl $loopLabel""")
} else {
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
}
1 -> {
asmgen.out("""
dec $varname
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
else -> {
asmgen.out("""
dec $varname
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -692,13 +734,13 @@ $loopLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -707,20 +749,29 @@ $loopLabel""")
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel""")
}
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
@@ -81,8 +81,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
is PtIrRegister -> false
is PtMemoryByte -> arg.address !is PtNumber && arg.address !is PtIdentifier
is PtNumber -> false
is PtBool -> false
else -> true
@@ -90,7 +90,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
val registersUsed = mutableListOf<RegisterOrStatusflag>();
val registersUsed = mutableListOf<RegisterOrStatusflag>()
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
@@ -110,9 +110,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, usedA())
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, usedA())
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, usedA())
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
@@ -205,7 +205,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
addr.add(value)
addr.parent = sub as PtNode
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
@@ -213,7 +213,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, target.position), scope)
}
return RegisterOrStatusflag(register, null)
}
@@ -70,7 +70,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
is PtNumber,
is PtBool,
is PtIdentifier,
is PtMachineRegister,
is PtIrRegister,
is PtArrayIndexer,
is PtPrefix,
is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ }
@@ -83,9 +83,80 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
val bittest = ifElse.condition as? PtBuiltinFunctionCall
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
val variable = bittest.args[0] as PtIdentifier
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
val testForBitSet = bittest.name.endsWith("_set")
when (bitnumber) {
7 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bmi $asmLabel")
}
else
translateIfElseBodies("bpl", ifElse)
} else {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bpl $asmLabel")
}
else
translateIfElseBodies("bmi", ifElse)
}
return
}
6 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvs $asmLabel")
}
else
translateIfElseBodies("bvc", ifElse)
} else {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvc $asmLabel")
}
else
translateIfElseBodies("bvs", ifElse)
}
return
}
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
}
}
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
assignConditionValueToRegisterAndTest(ifElse.condition)
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
if(jumpAfterIf!=null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, ifElse.elseScope)
else
@@ -193,7 +264,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
else
translateIfElseBodies("beq", stmt)
} else {
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // TODO should have no more of these at all
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // should not occur ;-)
assignConditionValueToRegisterAndTest(stmt.condition)
if(jumpAfterIf!=null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
@@ -405,14 +476,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
@@ -616,7 +687,7 @@ _jump jmp ($asmLabel)
if(right is PtIdentifier) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, signed)
val variable = asmgen.asmVariableName(right)
return code(variable, variable+"+1")
return code(variable, "$variable+1")
}
// TODO optimize for simple array value
@@ -737,7 +808,7 @@ _jump jmp ($asmLabel)
if(right is PtIdentifier) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, signed)
val variable = asmgen.asmVariableName(right)
return code(variable, variable+"+1")
return code(variable, "$variable+1")
}
// TODO optimize for simple array value
@@ -848,10 +919,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@@ -985,10 +1056,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@@ -1514,7 +1585,7 @@ _jump jmp ($asmLabel)
else -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left)
translateAYNotEquals(varname, varname + "+1")
translateAYNotEquals(varname, "$varname+1")
}
}
}
@@ -1562,7 +1633,7 @@ _jump jmp ($asmLabel)
else -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left)
translateAYEquals(varname, varname+"+1")
translateAYEquals(varname, "$varname+1")
}
}
}
@@ -89,6 +89,12 @@ internal class ProgramAndVarsGen(
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
}
OutputType.PRG -> {
when(options.launcher) {
@@ -103,13 +109,20 @@ internal class ProgramAndVarsGen(
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
// this is the same as RAW
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
@@ -119,6 +132,9 @@ internal class ProgramAndVarsGen(
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
@@ -149,7 +165,10 @@ internal class ProgramAndVarsGen(
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
asmgen.out(" jmp sys.cleanup_at_exit")
}
else -> asmgen.jmp("p8b_main.p8s_start")
else -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp sys.cleanup_at_exit")
}
}
}
@@ -451,22 +470,10 @@ internal class ProgramAndVarsGen(
}
private fun entrypointInitialization() {
asmgen.out("; program startup initialization")
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
// set full BSS area to zero
asmgen.out("""
.if prog8_bss_section_size>0
; reset all variables in BSS section to zero
lda #<prog8_bss_section_start
ldy #>prog8_bss_section_start
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #<prog8_bss_section_size
ldy #>prog8_bss_section_size
lda #0
jsr prog8_lib.memset
.endif""")
// zero out the BSS area first, before setting the variable init values
asmgen.out(" jsr prog8_lib.program_startup_clear_bss")
// initialize block-level (global) variables at program start
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
@@ -517,9 +524,7 @@ internal class ProgramAndVarsGen(
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
}
asmgen.out("""+
clv
clc""")
asmgen.out("+")
}
private class ZpStringWithInitial(
@@ -579,7 +584,7 @@ internal class ProgramAndVarsGen(
asmgen.out("")
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables without initialization value")
asmgen.out("; non-zeropage variables")
asmgen.out(" .section BSS")
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
uninitializedVariable2asm(it)
@@ -588,7 +593,7 @@ internal class ProgramAndVarsGen(
}
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
asmgen.out("; non-zeropage variables with init value")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
stringvars.forEach {
outputStringvar(
@@ -635,8 +640,7 @@ internal class ProgramAndVarsGen(
} else 0
when (variable.dt) {
DataType.BOOL -> TODO("bool var to asm")
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
@@ -48,9 +48,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
val numberOfAllocatableVariables = allVariables.size
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
val varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }
val numberOfExplicitNonZpVariables = allVariables.count { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
var numberOfNonIntegerVariables = 0
@@ -86,7 +86,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// try to allocate any other interger variables into the zeropage until it is full.
// try to allocate the "don't care" interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) {
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
@@ -110,9 +110,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
}
}
// println(" number of allocated vars: $numberOfAllocatableVariables")
// println(" put into zeropage: $numVariablesAllocatedInZP, non-zp allocatable: ${numberOfNonIntegerVariables+numberOfExplicitNonZpVariables}")
// println(" zeropage free space: ${zeropage.free.size} bytes")
// note: no zeropage allocation is done at all for the @nozp variables. This means they will always end up outside the zeropage.
}
private fun collectAllVariables(st: SymbolTable): Collection<StStaticVariable> {
@@ -35,7 +35,7 @@ internal class AnyExprAsmGen(
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
"both operands must be words"
}
return assignWordBinExpr(expr)
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
DataType.FLOAT -> {
require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
@@ -47,30 +47,6 @@ internal class AnyExprAsmGen(
}
}
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
when(expr.operator) {
"+" -> TODO("word + at ${expr.position}")
"-" -> TODO("word - at ${expr.position}")
"*" -> TODO("word * at ${expr.position}")
"/" -> TODO("word / at ${expr.position}")
"<<" -> TODO("word << at ${expr.position}")
">>" -> TODO("word >> at ${expr.position}")
"%" -> TODO("word % at ${expr.position}")
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
"&" -> TODO("word and at ${expr.position}")
"|" -> TODO("word or at ${expr.position}")
"^", "xor" -> TODO("word xor at ${expr.position}")
"==" -> TODO("word == at ${expr.position}")
"!=" -> TODO("word != at ${expr.position}")
"<" -> TODO("word < at ${expr.position}")
"<=" -> TODO("word <= at ${expr.position}")
">" -> TODO("word > at ${expr.position}")
">=" -> TODO("word >= at ${expr.position}")
else -> return false
}
}
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
@@ -89,21 +65,11 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"*" -> {
TODO("byte * at ${expr.position}")
}
"/" -> {
TODO("byte / at ${expr.position}")
}
"<<" -> {
TODO("byte << at ${expr.position}")
}
">>" -> {
TODO("byte >> at ${expr.position}")
}
"%" -> {
TODO("byte % at ${expr.position}")
}
"*" -> TODO("byte * at ${expr.position}")
"/" -> TODO("byte / at ${expr.position}")
"<<" -> TODO("byte << at ${expr.position}")
">>" -> TODO("byte >> at ${expr.position}")
"%" -> TODO("byte % at ${expr.position}")
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
@@ -130,24 +96,12 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"==" -> {
TODO("byte == at ${expr.position}")
}
"!=" -> {
TODO("byte != at ${expr.position}")
}
"<" -> {
TODO("byte < at ${expr.position}")
}
"<=" -> {
TODO("byte <= at ${expr.position}")
}
">" -> {
TODO("byte > at ${expr.position}")
}
">=" -> {
TODO("byte >= at ${expr.position}")
}
"==" -> TODO("byte == at ${expr.position}")
"!=" -> TODO("byte != at ${expr.position}")
"<" -> TODO("byte < at ${expr.position}")
"<=" -> TODO("byte <= at ${expr.position}")
">" -> TODO("byte > at ${expr.position}")
">=" -> TODO("byte >= at ${expr.position}")
else -> return false
}
}
@@ -221,17 +175,14 @@ internal class AnyExprAsmGen(
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
// this requires some more forced copying around of float values in certain cases
if (right.isSimple()) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
// 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
// Unfortunately this means we have to push and pop an intermediary floating point value to and from memory.
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
Cx16Target.NAME -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
@@ -1,6 +1,8 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StMemVar
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
@@ -9,11 +11,12 @@ import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.returnsWhatWhere
internal class AssignmentAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val options: CompilationOptions,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator) {
internal class AssignmentAsmGen(
private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator
) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
fun translate(assignment: PtAssignment) {
@@ -32,6 +35,164 @@ internal class AssignmentAsmGen(private val program: PtProgram,
augmentableAsmGen.translate(assign, augmentedAssign.definingISub())
}
fun translateMultiAssign(assignment: PtAssignment) {
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val sub = asmgen.symbolTable.lookup(values.name) as? StRomSub
?: throw AssemblyError("only asmsubs can return multiple values")
require(sub.returns.size>=2)
if(sub.returns.any { it.type==DataType.FLOAT })
TODO("deal with (multiple?) FP return registers")
asmgen.translate(values)
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if (statusFlagResults.isEmpty())
assignRegisterResults(registersResults)
else if(registersResults.isEmpty())
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
else
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
}
private fun assignStatusFlagsAndRegistersResults(
statusFlagResults: List<Pair<StRomSubParameter, PtNode>>,
registersResults: List<Pair<StRomSubParameter, PtNode>>
) {
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
if(registersResults.isEmpty())
false
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
false
else
true
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
} else {
val saveA = needsToSaveA(registersResults)
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
assignRegisterResults(registersResults)
}
}
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StRomSubParameter, PtNode>>) {
// assigning flags to their variables targets requires load-instructions that destroy flags
// so if there's more than 1, we need to save and restore the flags
val saveFlags = statusFlagResults.size>1
fun hasFlag(statusFlagResults: List<Pair<StRomSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
for ((returns, target) in statusFlagResults) {
if(returns.register.statusflag!! == flag)
return target as PtAssignTarget
}
return null
}
val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc)
val targetZero = hasFlag(statusFlagResults, Statusflag.Pz)
val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn)
val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv)
if(saveA) asmgen.out(" pha")
if(targetZero!=null && !targetZero.void)
assignZeroFlagResult(targetZero, saveFlags)
if(targetNeg!=null && !targetNeg.void)
assignNegativeFlagResult(targetNeg, saveFlags)
if(targetCarry!=null && !targetCarry.void)
assignCarryFlagResult(targetCarry)
if(targetOverflow!=null && !targetOverflow.void)
assignOverflowFlagResult(targetOverflow)
if(saveA) asmgen.out(" pla")
}
private fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
private fun assignCarryFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
private fun assignZeroFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
private fun assignNegativeFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
private fun assignOverflowFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
when(assign.source.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
@@ -60,7 +221,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.BOOL -> {
if (assign.source.datatype == DataType.BOOL || !options.strictBool) assignVariableByte(assign.target, variable)
if (assign.source.datatype == DataType.BOOL) assignVariableByte(assign.target, variable)
else throw AssemblyError("assigning non-bool variable to boolean, should have been typecasted")
}
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
@@ -143,12 +304,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: PtExpression) {
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
}
val value = assign.source.memory!!
when (value.address) {
is PtNumber -> {
@@ -163,10 +318,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
assignRegisterByte(assign.target, CpuRegister.A, false, true)
} else {
assignViaExprEval(value.address)
assignByteFromAddressExpression(value.address, assign.target)
}
}
else -> assignViaExprEval(value.address)
else -> assignByteFromAddressExpression(value.address, assign.target)
}
}
SourceStorageKind.EXPRESSION -> {
@@ -178,6 +333,106 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignByteFromAddressExpression(address: PtExpression, target: AsmAssignTarget) {
if(address is PtBinaryExpression) {
if(address.operator=="+" && address.right.type==DataType.UWORD) {
if (address.left is PtIdentifier) {
// use (zp),Y instead of explicitly calculating the full zp pointer value
val pointer = (address.left as PtIdentifier).name
when(val index=address.right) {
is PtIdentifier -> {
val indexName = index.name
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc $indexName+1
sta P8ZP_SCRATCH_W2+1
ldy $indexName
lda (P8ZP_SCRATCH_W2),y""")
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
is PtNumber -> {
val indexValue = index.number.toInt().toString()
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc #>$indexValue
sta P8ZP_SCRATCH_W2+1
ldy #<$indexValue
lda (P8ZP_SCRATCH_W2),y""")
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
else -> {}
}
}
}
// else if(address.operator=="-") {
// // does this ever occur? we could optimize it too, but it seems like a pathological case
// }
}
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
private fun storeByteInAToAddressExpression(address: PtExpression, saveA: Boolean) {
if(address is PtBinaryExpression) {
if(address.operator=="+") {
if (address.left is PtIdentifier && address.right.type==DataType.UWORD) {
// use (zp),Y instead of explicitly calculating the full zp pointer value
val pointer = (address.left as PtIdentifier).name
when(val index=address.right) {
is PtIdentifier -> {
val indexName = index.name
asmgen.out("""
tax
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc $indexName+1
sta P8ZP_SCRATCH_W2+1
ldy $indexName
txa
sta (P8ZP_SCRATCH_W2),y""")
return
}
is PtNumber -> {
val indexValue = index.number.toInt().toString()
asmgen.out("""
tax
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc #>$indexValue
sta P8ZP_SCRATCH_W2+1
ldy #<$indexValue
txa
sta (P8ZP_SCRATCH_W2),y""")
return
}
else -> {}
}
}
}
// else if(address.operator=="-") {
// // does this ever occur? we could optimize it too, but it seems like a pathological case
// }
}
if(saveA) asmgen.out(" pha")
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
if(saveA) asmgen.out(" pla")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
@@ -562,8 +817,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.Y -> "y"
else -> TODO("comparison to word register")
}
val assignTrue = PtInlineAssembly(" ld${reg} #1", false, assign.target.position)
val assignFalse = PtInlineAssembly(" ld${reg} #0", false, assign.target.position)
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
ifPart.add(assignTrue)
elsePart.add(assignFalse)
val ifelse = PtIfElse(assign.position)
@@ -597,7 +852,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignTrue.add(target)
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
} else {
require(assign.target.datatype in ByteDatatypes)
when(assign.target.kind) {
TargetStorageKind.VARIABLE -> {
if(assign.target.datatype in WordDatatypes) {
@@ -614,11 +868,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sta ${assign.target.asmVarname}+1""", false, assign.target.position)
}
} else {
assignTrue = PtInlineAssembly(" lda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
assignTrue = PtInlineAssembly("\tlda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
}
}
TargetStorageKind.MEMORY -> {
val tgt = PtAssignTarget(assign.target.position)
val tgt = PtAssignTarget(false, assign.target.position)
val targetmem = assign.target.memory!!
val mem = PtMemoryByte(targetmem.position)
mem.add(targetmem.address)
@@ -628,7 +882,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
}
TargetStorageKind.ARRAY -> {
val tgt = PtAssignTarget(assign.target.position)
val tgt = PtAssignTarget(false, assign.target.position)
val targetarray = assign.target.array!!
val array = PtArrayIndexer(assign.target.datatype, targetarray.position)
array.add(targetarray.variable)
@@ -667,7 +921,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
internal fun directIntoY(expr: PtExpression): Boolean {
return when(expr) {
is PtIdentifier -> true
is PtMachineRegister -> true
is PtIrRegister -> true
is PtNumber -> true
is PtBuiltinFunctionCall -> expr.name in arrayOf("lsb", "msb")
else -> false
@@ -699,6 +953,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun optimizedDivideExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
// replacing division by shifting is done in an optimizer step.
when(expr.type) {
DataType.UBYTE -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
@@ -870,7 +1125,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
} else {
if (signed && shifts > 0) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
if(shifts==1)
asmgen.out(" cmp #$80 | ror a")
else
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
@@ -904,7 +1162,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
} else {
if(signed && shifts>0) {
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
if(shifts==1) {
asmgen.out("""
pha
tya
cmp #$80
ror a
tay
pla
ror a""")
}
else
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
@@ -968,10 +1237,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtNumber -> {
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
if(expr.operator=="+")
asmgen.out(" clc | adc #${right.number.toHex()}")
else
asmgen.out(" sec | sbc #${right.number.toHex()}")
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (expr.operator == "+")
asmgen.out(" inc a")
else
asmgen.out(" dec a")
} else {
if (expr.operator == "+")
asmgen.out(" clc | adc #${right.number.toHex()}")
else
asmgen.out(" sec | sbc #${right.number.toHex()}")
}
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
}
@@ -1106,8 +1382,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtNumber -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
if(expr.operator=="+") {
asmgen.out("""
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(expr.operator=="+") {
asmgen.out("""
inc a
bne +
iny
+""")
} else {
asmgen.out("""
dec a
cmp #255
bne +
dey
+""")
}
} else if(dt!=DataType.WORD && right.number.toInt() in 0..255) {
if(expr.operator=="+") {
asmgen.out("""
clc
adc #${right.number.toHex()}
bcc +
iny
+""") } else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #${right.number.toHex()}
bcs +
dey
+""")
}
} else {
if(expr.operator=="+") {
asmgen.out("""
clc
adc #<${right.number.toHex()}
tax
@@ -1115,8 +1422,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
adc #>${right.number.toHex()}
tay
txa""")
} else if(expr.operator=="-") {
asmgen.out("""
} else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #<${right.number.toHex()}
tax
@@ -1124,6 +1431,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sbc #>${right.number.toHex()}
tay
txa""")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
@@ -1195,11 +1503,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
val address = mem.address as? PtBinaryExpression
if(address is PtBinaryExpression) {
val constOffset = address.right.asConstInteger()
// check that the offset is actually a byte (so that it fits in a single register)
if(constOffset==null && address.right.type !in ByteDatatypes)
return false
if(constOffset!=null && constOffset !in -128..255)
return false
val ptrVar = address.left as? PtIdentifier
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
val constOffset = address.right.asConstInteger()
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
asmgen.out(" ldy #$constOffset")
@@ -1454,6 +1767,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
in ByteDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out("""
cmp #0
beq +
lda #1
+ eor #1""")
@@ -1516,9 +1830,56 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun containmentCheckIntoA(containment: PtContainmentCheck) {
val elementDt = containment.element.type
val symbol = asmgen.symbolTable.lookup(containment.iterable.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingSub())
val elementDt = containment.needle.type
if(containment.haystackValues!=null) {
val haystack = containment.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in ByteDatatypesWithBoolean -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
for(number in haystack) {
asmgen.out("""
cmp #$number
beq +""")
}
asmgen.out("""
lda #0
beq ++
+ lda #1
+""")
}
in WordDatatypes -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
assignExpressionToRegister(containment.needle, RegisterOrPair.AY, elementDt == DataType.WORD)
val gottemLabel = asmgen.makeLabel("gottem")
val endLabel = asmgen.makeLabel("end")
for(number in haystack) {
asmgen.out("""
cmp #<$number
bne +
cpy #>$number
beq $gottemLabel
+ """)
}
asmgen.out("""
lda #0
beq $endLabel
$gottemLabel lda #1
$endLabel""")
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
return
}
val symbol = asmgen.symbolTable.lookup(containment.haystackHeapVar!!.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingISub())
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
@@ -1526,7 +1887,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
when(dt) {
DataType.STR -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position,"P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1534,13 +1895,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_F -> {
assignExpressionToRegister(containment.element, RegisterOrPair.FAC1, true)
assignExpressionToRegister(containment.needle, RegisterOrPair.FAC1, true)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr floats.containment_floatarray")
}
DataType.ARRAY_B, DataType.ARRAY_UB -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1548,7 +1909,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt)
assignExpressionToVariable(containment.needle, "P8ZP_SCRATCH_W1", elementDt)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W2"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_wordarray")
@@ -1662,8 +2023,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when (valueDt) {
DataType.BOOL -> {
if(targetDt in ByteDatatypes) {
// optimization to assign boolean expression to byte target (just assign the 0 or 1 directly, no cast needed)
if(targetDt in IntegerDatatypes) {
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
val assignDirect = AsmAssignment(
AsmAssignSource.fromAstSource(value, program, asmgen),
target,
@@ -1672,7 +2033,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
)
assignExpression(assignDirect, target.scope)
} else {
throw AssemblyError("expected bool or byte target type")
TODO("assign bool to non-integer type $targetDt")
}
}
in ByteDatatypes -> {
@@ -2182,7 +2543,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc #$constIndex
bne +
bcc +
iny
+""")
}
@@ -2207,7 +2568,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bne +
bcc +
iny
+""")
}
@@ -2217,7 +2578,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
ldy #>$sourceName
clc
adc #<$sourceName
bne +
bcc +
iny
+""")
}
@@ -3002,11 +3363,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else {
if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | phx")
RegisterOrPair.AY -> asmgen.out(" pha | phy")
RegisterOrPair.XY -> asmgen.out(" phx | phy")
else -> throw AssemblyError("expected reg pair")
}
} else {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
@@ -3045,11 +3415,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else {
if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | phx")
RegisterOrPair.AY -> asmgen.out(" pha | phy")
RegisterOrPair.XY -> asmgen.out(" phx | phy")
else -> throw AssemblyError("expected reg pair")
}
} else {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
@@ -3548,17 +3927,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
fun storeViaExprEval() {
when(addressExpr) {
is PtNumber, is PtIdentifier -> {
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.out(" pla")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
is PtNumber, is PtIdentifier -> storeByteInAToAddressExpression(addressExpr, false)
else -> storeByteInAToAddressExpression(addressExpr, true)
}
}
@@ -225,7 +225,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.LITERALNUMBER -> inplacemodificationSplitWordWithLiteralval(target.asmVarname, target.datatype, index, operator, value.number!!.number.toInt())
else -> {
// TODO: more optimized code for VARIABLE, REGISTER, MEMORY, ARRAY, EXPRESSION in the case of split-word arrays
val scope = target.origAstTarget?.definingSub()
val scope = target.origAstTarget?.definingISub()
val regTarget = AsmAssignTarget.fromRegisters(RegisterOrPair.R0, false, target.position, scope, asmgen)
val assignToReg = AsmAssignment(value, regTarget, program.memsizer, target.position)
assignmentAsmGen.translateNormalAssignment(assignToReg, scope)
@@ -1086,6 +1086,21 @@ $shortcutLabel:""")
}
}
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(operator=="&" && value is PtPrefix && value.operator=="~") {
// M &= ~A --> use TRB 65c02 instruction for that
asmgen.assignExpressionToRegister(value.value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out(" trb $name")
return
}
else if(operator=="|") {
// M |= A --> use TSB 65c02 instruction for that
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out(" tsb $name")
return
}
}
// normal evaluation
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
@@ -1094,6 +1109,15 @@ $shortcutLabel:""")
private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) {
// note: no logical and/or shortcut here, not worth it due to simple right operand
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(operator=="|") {
// M |= A --> use TSB 65c02 instruction for that
asmgen.out(" lda $otherName | tsb $name")
return
}
}
asmgen.out(" lda $name")
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
asmgen.out(" sta $name")
@@ -1459,6 +1483,7 @@ $shortcutLabel:""")
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
}
"/" -> {
// replacing division by shifting is done in an optimizer step.
if (dt == DataType.UBYTE)
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
else
@@ -1642,7 +1667,7 @@ $shortcutLabel:""")
}
private fun immediateOrInplace(name: String, value: Int) {
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out(" lda #$value | tsb $name") // set bit
} else {
asmgen.out(" lda $name | ora #$value | sta $name")
@@ -1703,7 +1728,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
inplacemodificationSomeWordWithLiteralval(name, name + "+1", dt, operator, value, block)
inplacemodificationSomeWordWithLiteralval(name, "$name+1", dt, operator, value, block)
}
private fun inplacemodificationSomeWordWithLiteralval(lsb: String, msb: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
@@ -1807,33 +1832,36 @@ $shortcutLabel:""")
}
}
"/" -> {
if(value==0)
// replacing division by shifting is done in an optimizer step.
if(value==0) {
throw AssemblyError("division by zero")
if(dt==DataType.WORD) {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
sta $lsb
sty $msb
""")
}
else {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
sta $lsb
sty $msb
""")
} else {
if(dt==DataType.WORD) {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
sta $lsb
sty $msb
""")
}
else {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
sta $lsb
sty $msb
""")
}
}
}
"%" -> {
+4 -4
View File
@@ -53,7 +53,7 @@ class TestCodegen: FunSpec({
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
@@ -68,7 +68,7 @@ class TestCodegen: FunSpec({
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
@@ -76,7 +76,7 @@ class TestCodegen: FunSpec({
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
@@ -84,7 +84,7 @@ class TestCodegen: FunSpec({
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
+3 -4
View File
@@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -29,7 +28,7 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
}
+5 -5
View File
@@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -28,10 +27,11 @@ dependencies {
implementation project(':intermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -1,5 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
@@ -8,16 +10,70 @@ import prog8.intermediate.*
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): IRCodeChunks {
if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
if(assignment.multiTarget) {
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val chunks = translateRegularAssign(assignment)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
return chunks
val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub
?: throw AssemblyError("only asmsubs can return multiple values")
val result = mutableListOf<IRCodeChunkBase>()
val funcCall = this.expressionEval.translate(values)
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
if(funcCall.multipleResultFpRegs.isNotEmpty())
TODO("deal with (multiple?) FP return registers")
val assignmentTargets = assignment.children.dropLast(1)
addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg)
if(sub.returns.size==assignmentTargets.size) {
// Targets and values match. Assign all the things. Skip 'void' targets.
sub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
val target = it.first.second as PtAssignTarget
if(!target.void) {
val regNumber = it.second
val returns = it.first.first
result += assignCpuRegister(returns, regNumber, target)
}
}
} else {
throw AssemblyError("number of values and targets don't match")
}
return result
} else {
if (assignment.target.children.single() is PtIrRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return translateRegularAssign(assignment)
}
}
private fun assignCpuRegister(returns: StRomSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val loadCpuRegInstr = when(returns.register.registerOrPair) {
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.X -> IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.Y -> IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
null -> {
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
else -> throw AssemblyError("cannot load register")
}
addInstr(result, loadCpuRegInstr, null)
// build an assignment to store the value in the actual target.
val assign = PtAssignment(target.position)
assign.add(target)
assign.add(PtIrRegister(regNum, target.type, target.position))
result += translate(assign)
return result
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
if(augAssign.target.children.single() is PtMachineRegister)
// augmented assignment always has just a single target
if (augAssign.target.children.single() is PtIrRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
val target = augAssign.target
@@ -28,7 +84,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val array = target.array
val value = augAssign.value
val signed = target.type in SignedDatatypes
val result = when(augAssign.operator) {
val chunks = when (augAssign.operator) {
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"-=" -> operatorMinusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value)
@@ -50,9 +107,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
in PrefixOperators -> inplacePrefix(augAssign.operator, symbol, array, constAddress, memTarget, targetDt)
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
val chunks = if(result!=null) result else fallbackAssign(augAssign)
} ?: fallbackAssign(augAssign)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(augAssign.position)
return chunks
}
@@ -81,7 +136,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return translateRegularAssign(normalAssign)
}
private fun inplacePrefix(operator: String, symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType): IRCodeChunks? {
private fun inplacePrefix(operator: String, symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType): IRCodeChunks {
if(operator=="+")
return emptyList()
@@ -270,11 +325,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
else
throw AssemblyError("assignment value and target dt mismatch")
} else false
if (assignment.value is PtMachineRegister) {
valueRegister = (assignment.value as PtMachineRegister).register
if (assignment.value is PtIrRegister) {
valueRegister = (assignment.value as PtIrRegister).register
if(extendByteToWord) {
valueRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtIrRegister).register), null)
}
} else {
val tr = expressionEval.translateExpression(assignment.value)
@@ -444,9 +499,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addToResult(result, tr, tr.resultReg, -1)
return Pair(result, tr.resultReg)
}
val mult: PtExpression
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
val mult: PtExpression = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
val tr = expressionEval.translateExpression(mult)
@@ -1334,9 +1387,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val valueReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
@@ -1357,9 +1410,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
@@ -9,8 +9,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return when(call.name) {
"any" -> funcAny(call)
"all" -> funcAll(call)
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
@@ -19,6 +17,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
"call" -> funcCall(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
@@ -34,14 +33,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
"ror" -> funcRolRor(call)
"rol2" -> funcRolRor(call)
"ror2" -> funcRolRor(call)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
@@ -132,10 +131,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
if(call.void)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
return ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -151,6 +150,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
}
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentA = exprGen.translateExpression(call.args[2])
val argumentX = exprGen.translateExpression(call.args[3])
val argumentY = exprGen.translateExpression(call.args[4])
val argumentCarry = exprGen.translateExpression(call.args[5])
addToResult(result, bankTr, bankTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addToResult(result, argumentA, argumentA.resultReg, -1)
addToResult(result, argumentX, argumentX.resultReg, -1)
addToResult(result, argumentY, argumentY.resultReg, -1)
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg,
IRDataType.BYTE to argumentA.resultReg,
IRDataType.BYTE to argumentX.resultReg,
IRDataType.BYTE to argumentY.resultReg,
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
}
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val number = call.args[0]
@@ -204,79 +226,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
}
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val result = mutableListOf<IRCodeChunkBase>()
val lengthReg = codeGen.registers.nextFree()
if(arrayName.type in SplitWordArrayTypes) {
// any(lsb-array) or any(msb-array)
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trLsb.resultReg)
val shortcircuitLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = trLsb.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcircuitLabel)
it += IRInstruction(Opcode.PREPARECALL, immediate = 2)
}
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trMsb.resultReg)
addInstr(result, IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=trLsb.resultReg, reg2=trMsb.resultReg), null)
result += IRCodeChunk(shortcircuitLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, trLsb.resultReg, -1)
}
val syscall =
when (arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> IMSyscall.ANY_WORD
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
if(arrayName.type in SplitWordArrayTypes) {
// this is a bit complicated to calculate.... have to check all recombined (lsb,msb) words for $0000
TODO("all(split words $arrayName)")
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> IMSyscall.ALL_WORD
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
@@ -369,65 +318,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val lengthReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
if(arrayName.type in SplitWordArrayTypes) {
// reverse the lsb and msb arrays both, independently
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse")
}
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
DataType.ARRAY_W -> IMSyscall.SORT_WORD
DataType.STR -> IMSyscall.SORT_UBYTE
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
in SplitWordArrayTypes -> TODO("split word sort")
else -> throw IllegalArgumentException("weird type to sort")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.nextFree()
@@ -714,6 +604,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
if(arr.splitWords) TODO("IR rol/ror on split words array")
val variable = arr.variable.name
val itemsize = codeGen.program.memsizer.memorySize(arr.type)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = index*itemsize), null)
@@ -854,10 +745,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
val assignTarget = PtAssignTarget(false, target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position))
assignment.children.add(PtIrRegister(register, target.type, target.position))
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result
@@ -1,5 +1,6 @@
package prog8.codegen.intermediate
import prog8.code.StNode
import prog8.code.StRomSub
import prog8.code.StSub
import prog8.code.ast.*
@@ -7,8 +8,11 @@ import prog8.code.core.*
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
constructor(chunks: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int) : this(listOf(chunks), dt, resultReg, resultFpReg)
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int,
val multipleResultRegs: List<Int> = emptyList(), val multipleResultFpRegs: List<Int> = emptyList()
) {
constructor(chunk: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int, multipleResultRegs: List<Int> = emptyList(), multipleResultFpRegs: List<Int> = emptyList())
: this(listOf(chunk), dt, resultReg, resultFpReg, multipleResultRegs, multipleResultFpRegs)
companion object {
val EMPTY: ExpressionCodeResult = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
@@ -24,7 +28,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
return when (expr) {
is PtMachineRegister -> {
is PtIrRegister -> {
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
}
is PtBool -> {
@@ -164,55 +168,93 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val elementDt = check.needle.type
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
if(check.haystackValues!=null) {
val haystack = check.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in IntegerDatatypesWithBoolean -> {
if (elementDt in ByteDatatypesWithBoolean) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt in WordDatatypes) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
for(value in haystack){
it += IRInstruction(Opcode.CMPI, irType(elementDt), elementTr.resultReg, immediate = value)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = gottemLabel)
}
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 1), gottemLabel)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
}
val haystackVar = check.haystackHeapVar!!
when(haystackVar.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
else -> throw AssemblyError("weird iterable dt ${haystackVar.type} for ${haystackVar.name}")
}
}
@@ -448,7 +490,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
return translateStackFunctions(fcall, callTarget)
}
when(callTarget.scopedName) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLC), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.set_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.SEC), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.clear_irqd" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLI), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.set_irqd" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.SEI), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
}
when (callTarget) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
@@ -463,7 +534,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
result += tr.chunks
}
// return value
// return value (always singular for normal Subs)
val returnRegSpec = if(fcall.void) null else {
val returnIrType = irType(callTarget.returnType!!)
if(returnIrType==IRDataType.FLOAT)
@@ -472,7 +543,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
fcallArgs = FunctionCallArgs(argRegisters, if(returnRegSpec==null) emptyList() else listOf(returnRegSpec))), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@@ -493,44 +565,57 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
result += tr.chunks
when(parameter.register.registerOrPair) {
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
in Cx16VirtualRegisters -> {
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
}
null -> when(parameter.register.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> addInstr(result, IRInstruction(Opcode.LSR, paramDt, reg1=tr.resultReg), null)
else -> throw AssemblyError("weird statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
}
// return value
var statusFlagResult: Statusflag? = null
if(callTarget.returns.size>1)
return callRomSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
// return a single value (or nothing)
val returnRegSpec = if(fcall.void) null else {
if(callTarget.returns.isEmpty())
null
else if(callTarget.returns.size==1) {
else {
val returns = callTarget.returns[0]
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
if (returnIrType == IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else {
statusFlagResult = returns.register.statusflag
val returnRegister = if(statusFlagResult==null) codeGen.registers.nextFree() else -1
val returnRegister = codeGen.registers.nextFree()
FunctionCallArgs.RegSpec(returnIrType, returnRegister, returns.register)
}
} else {
// multiple return values: take the first *register* (not status flag) return value and ignore the rest.
val returns = callTarget.returns.first { it.register.registerOrPair!=null }
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
}
}
// create the call
val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec)
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
if(statusFlagResult!=null && returnRegSpec!=null) {
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
if(statusFlagResult!=null) {
// assign status flag bit to the return value register
finalReturnRegister = returnRegSpec.registerNum
if(finalReturnRegister<0)
@@ -573,6 +658,62 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
val chunk = mutableListOf<IRCodeChunkBase>()
when(callTarget.scopedName) {
"sys.push" -> {
// push byte
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.pushw" -> {
// push word
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
}
"sys.pop" -> {
// pop byte
val popReg = codeGen.registers.nextFree()
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
}
"sys.popw" -> {
// pop word
val popReg = codeGen.registers.nextFree()
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
}
else -> throw AssemblyError("unknown stack subroutine called")
}
}
private fun callRomSubWithMultipleReturnValues(
callTarget: StRomSub,
fcall: PtFunctionCall,
argRegisters: MutableList<FunctionCallArgs.ArgumentSpec>,
result: MutableList<IRCodeChunkBase>
): ExpressionCodeResult {
// return multiple values
val returnRegisters = callTarget.returns.map {
val regnum = if(it.type==DataType.FLOAT) codeGen.registers.nextFreeFloat() else codeGen.registers.nextFree()
FunctionCallArgs.RegSpec(irType(it.type), regnum, it.register)
}
// create the call
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
}
private fun operatorGreaterThan(
binExpr: PtBinaryExpression,
vmDt: IRDataType,
@@ -746,17 +887,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
return if(binExpr.right is PtNumber) {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(binExpr.right is PtBool) {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.XORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
return when (binExpr.right) {
is PtNumber -> {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
is PtBool -> {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
else -> {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.XORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
}
}
@@ -5,7 +5,6 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
import kotlin.io.path.readBytes
import kotlin.math.pow
class IRCodeGen(
@@ -52,9 +51,6 @@ class IRCodeGen(
optimizer.optimize(options.optimize, errors)
irProg.validate()
val regOptimizer = IRRegisterOptimizer(irProg)
regOptimizer.optimize()
return irProg
}
@@ -394,10 +390,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
when(iterable) {
is PtRange -> {
if(iterable.from is PtNumber && iterable.to is PtNumber)
result += translateForInConstantRange(forLoop, loopvar)
result += if(iterable.from is PtNumber && iterable.to is PtNumber)
translateForInConstantRange(forLoop, loopvar)
else
result += translateForInNonConstantRange(forLoop, loopvar)
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
@@ -491,49 +487,76 @@ class IRCodeGen(
val loopLabel = createLabelName()
val result = mutableListOf<IRCodeChunkBase>()
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
if(loopvarDtIr==IRDataType.BYTE && step==-1 && iterable.to.asConstInteger()==0) {
// downto 0 optimization (byte)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 255)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
else if(step==-1 && iterable.to.asConstInteger()==1) {
// downto 1 optimization (byte and word)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else {
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
}
return result
}
@@ -562,9 +585,25 @@ class IRCodeGen(
result += chunk
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) {
// downto 0 optimization (byte)
if(loopvarDt==DataType.BYTE || iterable.first<=127) {
chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel)
} else {
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else if(iterable.step==-1 && iterable.last==1) {
// downto 1 optimization (byte and word)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
} else {
// downto some other value
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += chunk2
return result
}
@@ -685,13 +724,11 @@ class IRCodeGen(
return code
}
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
@@ -715,7 +752,7 @@ class IRCodeGen(
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += if(knownAddress!=null)
@@ -788,19 +825,32 @@ class IRCodeGen(
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1 && !signed) {
code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
}
else if(pow2>=1 &&!signed) {
// just shift multiple bits
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(signed)
IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
else
IRInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2>=0) {
if(signed) {
if(pow2==1) {
// simple single bit shift (signed)
code += IRInstruction(Opcode.ASR, dt, reg1=reg)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
}
} else {
if(pow2==1) {
// simple single bit shift (unsigned)
code += IRInstruction(Opcode.LSR, dt, reg1=reg)
} else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSRN, dt, reg1 = reg, reg2 = pow2reg)
}
}
return code
} else {
// regular div
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
} else {
@@ -809,39 +859,56 @@ class IRCodeGen(
else
IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = factor)
}
return code
}
return code
}
internal fun divideByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int, signed: Boolean): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1 && !signed) {
// just simple bit shift
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
else
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2>=0) {
// can do bit shift instead of division
if(signed) {
if(pow2==1) {
// just simple bit shift (signed)
code += if (knownAddress != null)
IRInstruction(Opcode.ASRM, dt, address = knownAddress)
else
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
code += if (knownAddress != null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
} else {
if(pow2==1) {
// just simple bit shift (unsigned)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
else
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
}
else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
}
return code
}
else if(pow2>=1 && !signed) {
// just shift multiple bits
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(signed) {
if(knownAddress!=null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
else {
if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
} else {
else
{
// regular div
if (factor == 0) {
val reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
@@ -866,8 +933,8 @@ class IRCodeGen(
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
}
}
return code
}
return code
}
private fun translate(ifElse: PtIfElse): IRCodeChunks {
@@ -1294,6 +1361,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
val tr = expressionEval.translateExpression(condition)
result += tr.chunks
if(ifElse.hasElse()) {
@@ -1451,6 +1522,10 @@ class IRCodeGen(
}
when(val cond=ifElse.condition) {
is PtBool -> {
// normally this will be optimized away, but not with -noopt
translateSimple(cond, Opcode.BSTEQ)
}
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
translateSimple(cond, Opcode.BSTEQ)
@@ -1502,17 +1577,16 @@ class IRCodeGen(
private fun translate(jump: PtJump): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
chunk += if(jump.address!=null) {
IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.identifier != null) {
if(isIndirectJump(jump)) {
chunk += IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
} else {
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
}
}
else
} else
throw AssemblyError("weird jump")
}
result += chunk
@@ -1617,7 +1691,7 @@ class IRCodeGen(
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = it.definingSub()!!.name + "." + it.name
val flattenedName = it.definingISub()!!.name + "." + it.name
if(symbolTable.lookup(flattenedName)==null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
@@ -1650,7 +1724,8 @@ class IRCodeGen(
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
}
val returnSpec = if(returns==null) null else FunctionCallArgs.RegSpec(returns.first, returns.second, null)
// for now, syscalls have 0 or 1 return value
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, returns.second, null))
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
}
}
@@ -52,15 +52,12 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
// TODO other optimizations
} while (changed)
}
}
removeEmptyChunks(sub)
}
// TODO also do register optimization step here at the end?
irprog.linkChunks() // re-link
}
@@ -182,8 +179,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
val lastChunk = chunks.last()
val candidate = sub.chunks[ix]
when(candidate) {
when(val candidate = sub.chunks[ix]) {
is IRCodeChunk -> {
if(mayJoinCodeChunks(lastChunk, candidate)) {
lastChunk.instructions += candidate.instructions
@@ -267,6 +263,24 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
}
if(ins.opcode== Opcode.SEI || ins.opcode== Opcode.CLI) {
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1]
if(insAfter.opcode == ins.opcode) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.SEI && insAfter.opcode== Opcode.CLI) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.CLI && insAfter.opcode== Opcode.SEI) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
}
return changed
}
@@ -433,21 +447,34 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
return false
/*
var changed = false
indexedInstructions.forEach { (idx, ins) ->
if(ins.opcode==Opcode.STOREM) {
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOADM) {
// loadm.X rX,something | storem.X rX,something ?? -> get rid of the store.
if(ins.labelSymbol!=null && ins.labelSymbol==prev.labelSymbol && ins.labelSymbolOffset==prev.labelSymbolOffset) {
changed=true
chunk.instructions.removeAt(idx)
}
else if(ins.address!=null && ins.address==prev.address) {
changed=true
chunk.instructions.removeAt(idx)
}
}
}
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
// TODO: detect multiple float ffrom/fto to the same target, only keep first
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
// TODO: detect multiple same ands, ors; only keep first
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
/*
Possible other optimizations:
// detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
// detect multiple stores to the same target, only keep first (if target is not I/O memory)
// detect multiple float ffrom/fto to the same target, only keep first
// detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
// detect multiple same ands, ors; only keep first
// detect multiple registers being assigned the same value (and not changed) - use only 1 of them (hard!)
// ...
*/
}
return changed
*/
}
}
@@ -1,103 +0,0 @@
package prog8.codegen.intermediate
import prog8.intermediate.IRProgram
class IRRegisterOptimizer(private val irProg: IRProgram) {
fun optimize() {
// reuseRegisters()
}
/*
TODO: this register re-use renumbering isn't going to work like this,
because subroutines will be clobbering the registers that the subroutine
which is calling them might be using...
private fun reuseRegisters() {
fun addToUsage(usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>>,
regnum: Int,
dt: IRDataType,
chunk: IRCodeChunkBase) {
val key = regnum to dt
val chunks = usage[key] ?: mutableSetOf()
chunks.add(chunk)
usage[key] = chunks
}
val usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>> = mutableMapOf()
irProg.foreachCodeChunk { chunk ->
chunk.usedRegisters().regsTypes.forEach { (regNum, types) ->
types.forEach { dt ->
addToUsage(usage, regNum, dt, chunk)
}
}
}
val registerReplacements = usage.asSequence()
.filter { it.value.size==1 }
.map { it.key to it.value.iterator().next() }
.groupBy({ it.second }, {it.first})
.asSequence()
.associate { (chunk, registers) ->
chunk to registers.withIndex().associate { (index, reg) -> reg to 50000+index }
}
registerReplacements.forEach { replaceRegisters(it.key, it.value) }
}
private fun replaceRegisters(chunk: IRCodeChunkBase, replacements: Map<Pair<Int, IRDataType>, Int>) {
val (rF, rI) = replacements.asSequence().partition { it.key.second==IRDataType.FLOAT }
val replacementsInt = rI.associate { it.key.first to it.value }
val replacementsFloat = rF.associate { it.key.first to it.value }
fun replaceRegs(fcallArgs: FunctionCallArgs?): FunctionCallArgs? {
if(fcallArgs==null)
return null
val args = if(fcallArgs.arguments.isEmpty()) fcallArgs.arguments else {
fcallArgs.arguments.map {
FunctionCallArgs.ArgumentSpec(
it.name,
it.address,
FunctionCallArgs.RegSpec(
it.reg.dt,
if(it.reg.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(it.reg.registerNum, it.reg.registerNum)
else
replacementsInt.getOrDefault(it.reg.registerNum, it.reg.registerNum),
it.reg.cpuRegister
)
)
}
}
val rt = fcallArgs.returns
val returns = if(rt==null) null else {
FunctionCallArgs.RegSpec(
rt.dt,
if(rt.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(rt.registerNum, rt.registerNum)
else
replacementsInt.getOrDefault(rt.registerNum, rt.registerNum),
rt.cpuRegister
)
}
return FunctionCallArgs(args, returns)
}
fun replaceRegs(instruction: IRInstruction): IRInstruction {
val reg1 = replacementsInt.getOrDefault(instruction.reg1, instruction.reg1)
val reg2 = replacementsInt.getOrDefault(instruction.reg2, instruction.reg2)
val fpReg1 = replacementsFloat.getOrDefault(instruction.fpReg1, instruction.fpReg1)
val fpReg2 = replacementsFloat.getOrDefault(instruction.fpReg2, instruction.fpReg2)
return instruction.copy(reg1 = reg1, reg2 = reg2, fpReg1 = fpReg1, fpReg2 = fpReg2, fcallArgs = replaceRegs(instruction.fcallArgs))
}
val newInstructions = chunk.instructions.map {
replaceRegs(it)
}
chunk.instructions.clear()
chunk.instructions.addAll(newInstructions)
}
*/
}
+35 -4
View File
@@ -80,21 +80,52 @@ class TestIRPeepholeOpt: FunSpec({
instr[1].opcode shouldBe Opcode.INC
}
test("remove double sec/clc") {
test("remove double sec/clc/sei/cli") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC)
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
))
irProg.chunks().single().instructions.size shouldBe 6
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.CLC
instr[1].opcode shouldBe Opcode.CLI
}
test("remove double sec/clc/sei/cli reversed") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.SEC
instr[1].opcode shouldBe Opcode.SEI
}
test("push followed by pop") {
+4 -4
View File
@@ -50,7 +50,7 @@ class TestVmCodeGen: FunSpec({
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
@@ -65,7 +65,7 @@ class TestVmCodeGen: FunSpec({
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
@@ -73,7 +73,7 @@ class TestVmCodeGen: FunSpec({
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
@@ -81,7 +81,7 @@ class TestVmCodeGen: FunSpec({
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
+3 -4
View File
@@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -27,7 +26,7 @@ dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
@@ -66,7 +66,7 @@ class ConstExprEvaluator {
// DataType.WORD -> result = result.toShort().toInt()
// else -> { /* keep as it is */ }
// }
return NumericLiteral.optimalNumeric(result.toDouble(), left.position)
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
@@ -133,7 +133,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -150,7 +150,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -167,7 +167,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -190,7 +190,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteral.optimalInteger(result, left.position)
NumericLiteral.optimalInteger(left.type, right.type, result, left.position)
}
DataType.FLOAT -> {
if(right.number==0.0) divideByZeroError(right.position)
@@ -219,7 +219,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
NumericLiteral.optimalNumeric(left.type, right.type, left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position)
@@ -33,6 +33,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.type==DataType.LONG) {
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=DataType.LONG)
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
@@ -91,7 +99,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
program.encoding.encodeString(leftString.value, leftString.encoding) + program.encoding.encodeString(rightString.value, rightString.encoding),
leftString.encoding)
}
val concatStr = StringLiteral(concatenated, leftString.encoding, expr.position)
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) {
@@ -99,7 +107,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
}
}
@@ -164,13 +172,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
if (expr.operator == "-") {
// X - -1 ---> X + 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
else if (expr.operator == "+") {
// X + -1 ---> X - 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
@@ -384,12 +392,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt istype DataType.LONG) {
return noModifications // this is handled in the numericalvalue case
}
if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
return noModifications
@@ -435,7 +444,6 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
subrightIsConst: Boolean): IAstModification?
{
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
if(expr.operator==subExpr.operator) {
// both operators are the same.
@@ -75,10 +75,11 @@ class VarConstantValueTypeAdjuster(
val declValue = decl.value?.constValue(program)
if (declValue != null) {
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
decl.value = null
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
IAstModification.ReplaceNode(decl, const, parent)
)
}
}
@@ -94,7 +95,7 @@ class VarConstantValueTypeAdjuster(
)
}
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
@@ -103,15 +104,15 @@ class VarConstantValueTypeAdjuster(
}
}
/*
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
}
}
}
*/
*/
}
return noModifications
@@ -153,11 +154,10 @@ class VarConstantValueTypeAdjuster(
if(func==listOf("clamp")) {
val t1 = functionCallExpr.args[0].inferType(program)
if(t1.isKnown) {
val replaceFunc: String
if(t1.isBytes) {
replaceFunc = if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
val replaceFunc = if(t1.isBytes) {
if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
replaceFunc = if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@@ -309,7 +309,7 @@ internal class ConstantIdentifierReplacer(
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
@@ -392,11 +392,35 @@ internal class ConstantIdentifierReplacer(
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// convert a range expression that is assigned to an array, to an array literal instead.
val range = assignment.value as? RangeExpression
if(range!=null) {
val targetDatatype = assignment.target.inferType(program)
if(targetDatatype.isArray) {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOr(DataType.UNDEFINED),
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
assignment.value, false, false, Position.DUMMY)
val replaceValue = createConstArrayInitializerValue(decl)
if(replaceValue!=null) {
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
}
}
}
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
val rangeSize=(decl.value as? RangeExpression)?.size()
if(rangeSize!=null && rangeSize>65535) {
errors.err("range size overflow", decl.value!!.position)
return null
}
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
@@ -490,8 +514,7 @@ internal class ConstantIdentifierReplacer(
if(numericLv!=null) {
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
if(options.strictBool || numericLv.type !in ByteDatatypes)
errors.err("initializer value is not a boolean", numericLv.position)
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
@@ -4,7 +4,6 @@ import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
@@ -12,14 +11,9 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@@ -107,13 +101,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if (!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
if (expr.operator in AssociativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
if(parent !is Assignment || !(expr.left isSameAs parent.target) && maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr))
}
return noModifications
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
@@ -249,26 +237,24 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
// optimize boolean constant comparisons
if(expr.operator=="==") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
if(rightVal?.asBooleanValue==true)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool!=null) {
return if(rightConstBool)
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
}
}
if (rightVal?.number == 1.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
@@ -283,21 +269,16 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
}
}
if (rightVal?.number == 1.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
@@ -692,7 +673,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) {
} else if (cv in powersOfTwoFloat) {
expr.operator = "&"
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
return null
@@ -717,6 +698,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
return null
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
when (cv) {
0.0 -> return null // fall through to regular float division to properly deal with division by zero
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
@@ -744,12 +726,10 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
else -> return null
}
}
in powersOfTwo -> {
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
// Unsigned number divided by a power of two => shift right
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values), TODO is this correct???
// so we leave that as is and let the code generator deal with it.
val numshifts = log2(cv).toInt()
in powersOfTwoFloat -> {
val numshifts = powersOfTwoFloat.indexOf(cv)
if (leftDt in IntegerDatatypes) {
// division by a power of two => shift right (signed and unsigned)
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
@@ -801,14 +781,14 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
// left
return expr2.left
}
in powersOfTwo -> {
in powersOfTwoFloat -> {
if (leftValue.inferType(program).isInteger) {
// times a power of two => shift left
val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
in negativePowersOfTwoFloat -> {
if (leftValue.inferType(program).isInteger) {
// times a negative power of two => negate, then shift
val numshifts = log2(-cv).toInt()
@@ -59,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
val opti = ExpressionSimplifier(this, options, errors)
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this, errors)
opti.visit(this)
return opti.applyModifications()
}
+17 -12
View File
@@ -30,11 +30,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun visit(subroutine: Subroutine) {
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
if(!containsSubsOrVariables) {
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
if(subroutine !== program.entrypoint) {
if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine }
if (!containsSubsOrVariables) {
if (subroutine.statements.size == 1 || (subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))) {
if (subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
@@ -87,9 +87,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
} else
false
targetInline || valueInline
} else if(stmt.target.identifier!=null && stmt.isAugmentable) {
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
val binExpr = stmt.value as BinaryExpression
if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) {
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
makeFullyScoped(stmt.target.identifier!!)
makeFullyScoped(binExpr.left as IdentifierReference)
true
@@ -121,8 +121,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
}
if(subroutine.inline && subroutine.statements.size>1) {
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
if (subroutine.inline && subroutine.statements.size > 1) {
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
@@ -147,6 +147,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(call: FunctionCallStatement) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
@@ -169,6 +170,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(call: FunctionCallExpression) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
@@ -222,8 +224,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
noModifications
}
else -> {
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
if(origNode !== toInline) {
sub.hasBeenInlined = true
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
} else
noModifications
}
}
}
@@ -253,7 +258,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
is Return -> {
// is an expression, so we have to have a Return here in the inlined sub
// note that we don't have to process any args, because we online inline parameterless subroutines.
if(toInline.value!=null) {
if(toInline.value!=null && functionCallExpr!==toInline.value) {
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
}
@@ -21,7 +21,8 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
if("ignore_unused" !in parent.definingBlock.options())
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@@ -162,7 +163,7 @@ class StatementOptimizer(private val program: Program,
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -177,7 +178,7 @@ class StatementOptimizer(private val program: Program,
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -190,7 +191,7 @@ class StatementOptimizer(private val program: Program,
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@@ -199,35 +200,6 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
fun incOrDec(inc: Boolean): Assignment {
val pos = forLoop.position
val loopVar = forLoop.loopVar
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
return Assignment(AssignTarget(loopVar.copy(), null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = incOrDec(false)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
return noModifications
}
@@ -454,12 +426,6 @@ class StatementOptimizer(private val program: Program,
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
errors.info("for boolean condition a normal if statement is preferred", whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant
@@ -69,14 +69,14 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName) {
if(block.name != internedStringsModuleName && "ignore_unused" !in block.options()) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR}) {
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR} && "ignore_unused" !in block.options()) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
@@ -149,7 +149,8 @@ class UnusedCodeRemover(private val program: Program,
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
if("ignore_unused" !in decl.definingBlock.options())
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
+8 -7
View File
@@ -2,14 +2,14 @@ plugins {
id 'java'
id 'application'
id 'org.jetbrains.kotlin.jvm'
id 'com.github.johnrengelman.shadow' version '8.1.1'
// id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'io.github.goooler.shadow' version '8.1.7'
id 'com.peterabeles.gversion' version '1.10.2'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@@ -32,15 +32,16 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.antlr:antlr4-runtime:4.13.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation project(':codeCore')
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+2 -1
View File
@@ -15,7 +15,7 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="module" module-name="codeOptimizers" />
@@ -24,5 +24,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>
+82
View File
@@ -0,0 +1,82 @@
; any() and all() checks on arrays/memory buffers.
; These were builtin functions in older versions of the language.
%option no_symbol_prefixing, ignore_unused
anyall {
sub any(uword arrayptr, uword num_elements) -> bool {
; -- returns true if any byte in the array is not zero.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
for cx16.r0L in 0 to lsb(num_elements)-1 {
if cx16.r1[cx16.r0L]!=0
return true
}
return false
}
repeat num_elements {
if @(cx16.r1)!=0
return true
cx16.r1++
}
return false
}
sub all(uword arrayptr, uword num_elements) -> bool {
; -- returns true if all bytes in the array are not zero.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
for cx16.r0L in 0 to lsb(num_elements)-1 {
if cx16.r1[cx16.r0L]==0
return false
}
return true
}
repeat num_elements {
if @(cx16.r1)==0
return false
cx16.r1++
}
return true
}
sub anyw(uword arrayptr, uword num_elements) -> bool {
; -- returns true if any word in the array is not zero.
; doesn't work on @split arrays.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
repeat lsb(num_elements) {
if peekw(cx16.r1)!=0
return true
cx16.r1+=2
}
return false
}
repeat num_elements {
if peekw(cx16.r1)!=0
return true
cx16.r1+=2
}
return false
}
sub allw(uword arrayptr, uword num_elements) -> bool {
; -- returns true if all words in the array are not zero.
; doesn't work on @split arrays.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
repeat lsb(num_elements) {
if peekw(cx16.r1)==0
return false
cx16.r1+=2
}
return true
}
repeat num_elements {
if peekw(cx16.r1)==0
return false
cx16.r1+=2
}
return true
}
}
+44 -10
View File
@@ -23,16 +23,20 @@ sys {
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
const ubyte sizeof_bool = 1
const ubyte sizeof_byte = 1
const ubyte sizeof_ubyte = 1
const ubyte sizeof_word = 2
const ubyte sizeof_uword = 2
const ubyte sizeof_float = 0 ; undefined, no float support
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; TODO
%asm {{
sei
cld
clc
; TODO reset screen mode etc etc
clv
cli
rts
}}
@@ -40,7 +44,26 @@ sys {
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the Atari
cld
clc
clv
rts
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
@@ -248,27 +271,38 @@ save_SCRATCH_ZPWORD2 .word 0
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO where to store A as exit code?
%asm {{
sta cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
jmp cleanup_at_exit
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
; TODO where to store A,X,Y as exit code?
%asm {{
jmp exit
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
; TODO where to store A,X,Y,Carry as exit code?
%asm {{
jmp exit
sta cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
}}
}
+23 -20
View File
@@ -3,7 +3,6 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@@ -17,6 +16,10 @@ sub clear_screen() {
chrout(125)
}
sub cls() {
chrout(125)
}
sub nl() {
chrout('\n')
}
@@ -148,15 +151,15 @@ _tmp_outchar
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print zero terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
- lda (P8ZP_SCRATCH_B1),y
- lda (P8ZP_SCRATCH_W2),y
beq +
jsr chrout
iny
@@ -168,13 +171,13 @@ asmsub print (str text @ AY) clobbers(A,Y) {
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
jsr conv.internal_ubyte2decimal
pha
tya
jsr chrout
pla
jsr chrout
txa
jsr chrout
pla
jmp chrout
}}
}
@@ -182,21 +185,21 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
jsr conv.internal_ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr chrout
pla
txa
jsr chrout
jmp _ones
+ pla
cmp #'0'
+ cpx #'0'
beq _ones
txa
jsr chrout
_ones txa
_ones pla
jmp chrout
}}
}
@@ -210,7 +213,7 @@ asmsub print_b (byte value @ A) clobbers(A,X,Y) {
lda #'-'
jsr chrout
+ pla
jsr conv.byte2decimal
jsr conv.internal_byte2decimal
jmp print_ub._print_byte_digits
}}
}
@@ -223,7 +226,7 @@ asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
lda #'$'
jsr chrout
pla
+ jsr conv.ubyte2hex
+ jsr conv.internal_ubyte2hex
jsr chrout
tya
jmp chrout
@@ -277,9 +280,9 @@ asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
beq +
jsr chrout
iny
@@ -291,9 +294,9 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
@@ -303,7 +306,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
_gotdigit
jsr chrout
iny
lda conv.uword2decimal.decTenThousands,y
lda conv.internal_uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
+152
View File
@@ -0,0 +1,152 @@
; **experimental** buffer data structures, API subject to change!!
%option no_symbol_prefixing, ignore_unused
smallringbuffer {
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 255 bytes maximum.
; You can store and retrieve words too.
; It's optimized for speed and depends on the byte-wrap-around feature when doing incs and decs.
ubyte fill
ubyte head
ubyte tail
ubyte[256] buffer
sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines
head = fill = 0
tail = 255
}
sub put(ubyte value) -> bool {
; -- store a byte in the buffer, returns success
if fill==255
return false
buffer[head] = value
head++
fill++
return true
}
sub putw(uword value) -> bool {
; -- store a word in the buffer, returns success
if fill>=254
return false
fill += 2
buffer[head] = lsb(value)
head++
buffer[head] = msb(value)
head++
return true
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill==0 {
sys.clear_carry()
return 0
}
fill--
tail++
sys.set_carry()
return buffer[tail]
}
sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2
tail++
cx16.r0L = buffer[tail]
tail++
cx16.r0H = buffer[tail]
sys.set_carry()
return cx16.r0
}
}
ringbuffer {
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 8 KB maximum.
; You can store and retrieve words too.
uword fill
uword head
uword tail
uword buffer_ptr = memory("ringbuffer", 8192, 0)
sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines
head = fill = 0
tail = 8191
}
sub put(ubyte value) -> bool {
; -- store a byte in the buffer, returns success
if fill==8192
return false
buffer_ptr[head] = value
inc_head()
fill++
return true
}
sub putw(uword value) -> bool {
; -- store a word in the buffer, returns success
if fill>=8191
return false
fill += 2
buffer_ptr[head] = lsb(value)
inc_head()
buffer_ptr[head] = msb(value)
inc_head()
return true
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill==0 {
sys.clear_carry()
return 0
}
fill--
inc_tail()
cx16.r0L = buffer_ptr[tail]
sys.set_carry()
return cx16.r0L
}
sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2
inc_tail()
cx16.r0L = buffer_ptr[tail]
inc_tail()
cx16.r0H = buffer_ptr[tail]
sys.set_carry()
return cx16.r0
}
sub inc_head() {
head++
if msb(head)==$20
head=0
}
sub inc_tail() {
tail++
if msb(tail)==$20
tail=0
}
}
; TODO ringbuffer (FIFO queue) should use banked ram on the X16, but still work on virtual
; TODO stack (LIFO queue) using more than 1 page of ram (maybe even banked ram on the x16)
+28 -14
View File
@@ -93,11 +93,12 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
@@ -105,15 +106,20 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- utilities -----
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
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.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}
@@ -345,6 +351,14 @@ sys {
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
const ubyte sizeof_bool = 1
const ubyte sizeof_byte = 1
const ubyte sizeof_ubyte = 1
const ubyte sizeof_word = 2
const ubyte sizeof_uword = 2
const ubyte sizeof_float = 5
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
@@ -354,7 +368,6 @@ asmsub init_system() {
; Uppercase charset is activated.
%asm {{
sei
cld
lda #0
sta $ff00 ; select default bank 15
jsr cbm.IOINIT
@@ -367,8 +380,6 @@ asmsub init_system() {
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
@@ -376,7 +387,10 @@ asmsub init_system() {
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C128
cld
clc
clv
rts
}}
}
+7 -211
View File
@@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
@@ -13,11 +13,17 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@@ -288,216 +294,6 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
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
%asm {{
+45 -10
View File
@@ -4,11 +4,7 @@
%import shared_floats_functions
floats {
; ---- this block contains C-64 floating point related functions ----
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- this block contains C-64 floating point related functions ----
; ---- C64 basic and kernal ROM float constants and functions ----
@@ -24,7 +20,7 @@ romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value f
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
@@ -40,7 +36,7 @@ romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
; there is also floats.FREAD{S,U}24AXY that read (un)signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
@@ -49,7 +45,7 @@ romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/2
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
@@ -67,7 +63,7 @@ romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mfl
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
@@ -108,7 +104,6 @@ asmsub FREADUS32 () clobbers(A,X,Y) {
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
%asm {{
sty $62
stx $63
@@ -123,6 +118,39 @@ asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)
}}
}
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
%asm{{
FAC = $61
sty FAC+1
stx FAC+2
sta FAC+3
cpy #$00
bne +
cpx #$00
bne +
cmp #$00
beq ++
+ ldx #$98
bit FAC+1
bmi +
- dex
asl FAC+3
rol FAC+2
rol FAC+1
bpl -
+ stx FAC
lda #$00
sta FAC+4
sta FAC+5
rts
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
@@ -181,6 +209,13 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
}}
}
; get the jiffy clock as a float
asmsub time() -> float @ FAC1 {
%asm {{
jsr cbm.RDTIM
jmp floats.FREADU24AXY
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
-122
View File
@@ -6,108 +6,6 @@ func_sign_f_into_A .proc
jmp SIGN
.pend
func_swap_f .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
func_reverse_f .proc
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
_left_index = P8ZP_SCRATCH_W2
_right_index = P8ZP_SCRATCH_W2+1
_loop_count = P8ZP_SCRATCH_REG
pha
jsr a_times_5
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
; copy right index float to left index float
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
a_times_5 .proc
sta P8ZP_SCRATCH_B1
@@ -118,26 +16,6 @@ a_times_5 .proc
rts
.pend
func_any_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_any_b_into_A
.pend
func_all_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_all_b_into_A
.pend
func_any_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_any_b_stack
.pend
func_all_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_all_b_stack
.pend
func_abs_f_into_FAC1 .proc
jsr MOVFM
jmp ABS
+20 -3
View File
@@ -288,17 +288,30 @@ hline_zero2
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Midpoint algorithm, filled.
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*2+1)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*2+1)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -307,6 +320,10 @@ hline_zero2
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*2+1)
horizontal_line(xcenter-yy, last_y4, yy*2+1)
}
+27 -14
View File
@@ -94,23 +94,29 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
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.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}
@@ -341,6 +347,13 @@ sys {
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
const ubyte sizeof_bool = 1
const ubyte sizeof_byte = 1
const ubyte sizeof_ubyte = 1
const ubyte sizeof_word = 2
const ubyte sizeof_uword = 2
const ubyte sizeof_float = 5
asmsub init_system() {
; Initializes the machine to a sane starting state.
@@ -351,7 +364,6 @@ asmsub init_system() {
; Uppercase charset is activated.
%asm {{
sei
cld
lda #%00101111
sta $00
lda #%00100111
@@ -366,8 +378,6 @@ asmsub init_system() {
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
@@ -375,7 +385,10 @@ asmsub init_system() {
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C64
cld
clc
clv
rts
}}
}
+6 -211
View File
@@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
@@ -13,11 +13,16 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@@ -292,216 +297,6 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PESCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
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
%asm {{
+409
View File
@@ -0,0 +1,409 @@
; **experimental** data compression/decompression routines, API subject to change!!
compression {
%option no_symbol_prefixing, ignore_unused
sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) {
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
; output_function = address of a routine that gets a byte arg in A,
; this is the next RLE byte to write to the compressed output buffer or file.
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
; compressed blocks (for instance if the source data is >64Kb)
; Worst case result storage size needed = (size + (size+126) / 127) + 1
; This routine is not optimized for speed but for readability and ease of use.
uword idx = 0
uword literals_start_idx = 0
ubyte literals_length = 0
asmsub call_output_function(ubyte arg @A) {
%asm {{
jmp (p8v_output_function)
}}
}
sub next_same_span() {
; returns length in cx16.r1L, and the byte value in cx16.r1H
cx16.r1H = data[idx]
cx16.r1L = 0
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
idx++
cx16.r1L++
}
}
sub output_literals() {
call_output_function(literals_length-1)
uword dataptr = data + literals_start_idx
ubyte i
for i in 0 to literals_length-1 {
call_output_function(@(dataptr))
dataptr++
}
literals_length = 0
}
while idx<size {
next_same_span() ; count in r1L, value in r1H
if cx16.r1L>1 {
; a replicate run
if literals_length>0
output_literals()
call_output_function((cx16.r1L^255)+2) ; 257-cx16.r1L
call_output_function(cx16.r1H)
}
else {
; add more to the literals run
if literals_length==128
output_literals()
if literals_length==0
literals_start_idx = idx-1
literals_length++
}
}
if literals_length>0
output_literals()
if is_last_block
call_output_function(128)
}
sub encode_rle(uword data, uword size, uword target, bool is_last_block) -> uword {
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
; Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1.
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
; compressed blocks (for instance if the source data is >64Kb)
; This routine is not optimized for speed but for readability and ease of use.
uword idx = 0
uword literals_start_idx = 0
ubyte literals_length = 0
uword orig_target = target
sub next_same_span() {
; returns length in cx16.r1L, and the byte value in cx16.r1H
cx16.r1H = data[idx]
cx16.r1L = 0
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
idx++
cx16.r1L++
}
}
sub output_literals() {
@(target) = literals_length-1
target++
uword dataptr = data + literals_start_idx
ubyte i
for i in 0 to literals_length-1 {
@(target) = @(dataptr)
target++
dataptr++
}
literals_length = 0
}
while idx<size {
next_same_span() ; count in r1L, value in r1H
if cx16.r1L>1 {
; a replicate run
if literals_length>0
output_literals()
@(target) = (cx16.r1L^255)+2 ; 257-cx16.r1L
target++
@(target) = cx16.r1H
target++
}
else {
; add more to the literals run
if literals_length==128
output_literals()
if literals_length==0
literals_start_idx = idx-1
literals_length++
}
}
if literals_length>0
output_literals()
if is_last_block {
@(target) = 128
target ++
}
return target-orig_target
}
asmsub decode_rle_srcfunc(uword source_function @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
; instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
; Stops decompressing when the maxsize has been reached.
%asm {{
sta _cb_mod1+1
sty _cb_mod1+2
sta _cb_mod2+1
sty _cb_mod2+2
sta _cb_mod3+1
sty _cb_mod3+2
lda cx16.r0L
ldy cx16.r0H
sta P8ZP_SCRATCH_W2 ; target ptr
sta _orig_target
sty P8ZP_SCRATCH_W2+1
sty _orig_target+1
lda cx16.r0L
clc
adc cx16.r1L
sta cx16.r1L
lda cx16.r0H
adc cx16.r1H
sta cx16.r1H ; decompression limit
_loop
; while target (W2) < limit (r1)
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
cmp cx16.r1L
tya
sbc cx16.r1H
bcs _end
_cb_mod1 jsr $ffff ; modified
bpl _literals
cmp #128
beq _end
; replicate next byte -n+1 times
eor #255
clc
adc #2
sta P8ZP_SCRATCH_REG
_cb_mod2 jsr $ffff ; modified
ldx P8ZP_SCRATCH_REG
ldy #0
- sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
; add A to target
lda P8ZP_SCRATCH_REG
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_literals
; copy the next n+1 bytes
pha
sta P8ZP_SCRATCH_B1
ldy #0
sty P8ZP_SCRATCH_REG
_cb_mod3 jsr $ffff ; modified
ldy P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W2),y
inc P8ZP_SCRATCH_REG
dec P8ZP_SCRATCH_B1
bpl _cb_mod3
; add N+1 to target
pla
sec
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_orig_target .word 0
_end
; return w2-orig_target, the size of the decompressed data
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sec
sbc _orig_target
tax
tya
sbc _orig_target+1
tay
txa
rts
}}
}
asmsub decode_rle(uword compressed @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
; Stops decompressing when the maxsize has been reached.
%asm {{
sta P8ZP_SCRATCH_W1 ; compressed data ptr
sty P8ZP_SCRATCH_W1+1
lda cx16.r0L
ldy cx16.r0H
sta P8ZP_SCRATCH_W2 ; target ptr
sta _orig_target
sty P8ZP_SCRATCH_W2+1
sty _orig_target+1
lda cx16.r0L
clc
adc cx16.r1L
sta cx16.r1L
lda cx16.r0H
adc cx16.r1H
sta cx16.r1H ; decompression limit
_loop ; while target (W2) < limit (r1)
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
cmp cx16.r1L
tya
sbc cx16.r1H
bcs _end
ldy #0
lda (P8ZP_SCRATCH_W1),y
bpl _literals
cmp #128
beq _end
; replicate next byte -n+1 times
eor #255
clc
adc #2
pha
tax
iny
lda (P8ZP_SCRATCH_W1),y
ldy #0
- sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
; add A to target
pla
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
; increase source by 2
clc
lda P8ZP_SCRATCH_W1
adc #2
sta P8ZP_SCRATCH_W1
lda #0
adc P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1+1
jmp _loop
_literals
; copy the next n+1 bytes
pha
tax
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
dex
bpl -
; add N+1 to source
pla
tax
sec
adc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
lda #0
adc P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1+1
; add N+1 to target as well
txa
sec
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_orig_target .word 0
_end
; return w2-orig_target, the size of the decompressed data
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sec
sbc _orig_target
tax
tya
sbc _orig_target+1
tay
txa
rts
}}
}
/***
; prog8 source code for the asm routine above:
sub decode_rle_prog8(uword @zp compressed, uword @zp target, uword maxsize) -> uword {
cx16.r0 = target ; original target
cx16.r1 = target+maxsize ; decompression limit
while target<cx16.r1 {
cx16.r2L = @(compressed)
if_neg {
if cx16.r2L==128
break
; replicate the next byte -n+1 times
compressed++
cx16.r3L = @(compressed)
repeat 2+(cx16.r2L^255) {
@(target) = cx16.r3L
target++
}
compressed++
} else {
; copy the next n+1 bytes
compressed++
repeat cx16.r2L+1 {
@(target) = @(compressed)
compressed++
target++
}
}
}
return target-cx16.r0
}
sub decode_rle_callback_prog8(uword producer_callback, uword @zp target, uword maxsize) -> uword {
cx16.r0 = target ; original target
cx16.r1 = target+maxsize ; decompression limit
while target<cx16.r1 {
cx16.r2L = lsb(call(producer_callback))
if_neg {
if cx16.r2L==128
break
; replicate the next byte -n+1 times
cx16.r3L = lsb(call(producer_callback))
repeat 2+(cx16.r2L^255) {
@(target) = cx16.r3L
target++
}
} else {
; copy the next n+1 bytes
repeat cx16.r2L+1 {
@(target) = lsb(call(producer_callback))
target++
}
}
}
return target-cx16.r0
}
***/
}
+95 -105
View File
@@ -8,97 +8,79 @@ conv {
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ub0(ubyte value @A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
jsr internal_ubyte2decimal
sty conv.string_out
stx conv.string_out+1
sta conv.string_out+2
lda #0
sta conv.string_out+3
lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_ub (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
sty P8ZP_SCRATCH_B1
jsr ubyte2decimal ; result in Y/A/X (100s, 10s, 1s).
; hundreds?
cpy #'0'
beq +
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
jmp _done
; tens?
+ cmp #'0'
beq +
sta string_out
stx string_out+1
lda #0
sta string_out+2
jmp _done
+ ; ones.
stx string_out
lda #0
sta string_out+1
_done lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ub(ubyte value @A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
jsr internal_ubyte2decimal
cpy #'0'
beq +
sty conv.string_out
stx conv.string_out+1
sta conv.string_out+2
lda #0
sta conv.string_out+3
jmp _done
+ cpx #'0'
beq +
stx conv.string_out
sta conv.string_out+1
lda #0
sta conv.string_out+2
jmp _done
+ sta conv.string_out
lda #0
sta conv.string_out+1
_done lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_b (byte value @ A) clobbers(X) -> str @AY {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
cmp #0
bpl +
ldy #'-'
sty string_out
ldy #1
+ sty P8ZP_SCRATCH_REG
jsr conv.byte2decimal ; result in Y/A/X (100s, 10s, 1s). and in uword2decimal.decHundreds, decTens, decOnes.
; hundreds?
cpy #'0'
bne _out_hundreds
ldy P8ZP_SCRATCH_REG
cmp #'0'
bne _out_tens
beq _out_ones
_out_hundreds
ldy P8ZP_SCRATCH_REG
lda uword2decimal.decHundreds
sta string_out,y
iny
_out_tens
lda uword2decimal.decTens
sta string_out,y
iny
_out_ones
lda uword2decimal.decOnes
sta string_out,y
iny
lda #0
sta string_out,y
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_b(byte value @A) clobbers(X) -> str @AY {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
cmp #0
bpl str_ub
eor #255
clc
adc #1
jsr str_ub
; insert a minus sign at the start
lda #0
sta conv.string_out+4
lda conv.string_out+2
sta conv.string_out+3
lda conv.string_out+1
sta conv.string_out+2
lda conv.string_out
sta conv.string_out+1
lda #'-'
sta conv.string_out
lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out
sty string_out+1
lda #0
@@ -158,11 +140,11 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
%asm {{
pha
tya
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out+2
sty string_out+3
lda #0
@@ -176,9 +158,9 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
@@ -193,11 +175,11 @@ asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
asmsub str_uw (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda internal_uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
@@ -206,7 +188,7 @@ _output_digits
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
lda internal_uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
@@ -238,7 +220,7 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
adc #1
bcc +
iny
+ jsr conv.uword2decimal
+ jsr conv.internal_uword2decimal
ldx #1
bne str_uw._output_digits
rts
@@ -533,17 +515,25 @@ _stop
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
jmp uword2decimal.hex_try200
}}
asmsub internal_ubyte2decimal(ubyte value @A) -> ubyte @Y, ubyte @X, ubyte @A {
%asm {{
ldy #'0'-1
ldx #'9'+1
sec
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #'0'-1
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
asmsub internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; output in internal_uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
@@ -716,7 +706,7 @@ decOnes .byte 0
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
asmsub internal_byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
@@ -725,11 +715,11 @@ asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
eor #255
clc
adc #1
+ jmp ubyte2decimal
+ jmp internal_ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
asmsub internal_ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
pha
@@ -749,7 +739,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
asmsub internal_uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta P8ZP_SCRATCH_REG
Loaded 100 of 308 files, more files were not shown because too many files have changed in this diff. Show more