Compare commits

..
419 Commits
Author SHA1 Message Date
Irmen de Jong 3ea8ca59b7 beta 4 now with support for long values 2025-10-04 22:55:18 +02:00
Irmen de Jong 71ffbe2ba7 document the long type 2025-10-04 22:33:43 +02:00
Irmen de Jong e63921009c added math.mul32(), verafx.muls now returns long 2025-10-04 21:54:53 +02:00
Irmen de Jong db1aa3f257 math.crc32() now returns the crc value as a long 2025-10-04 17:40:49 +02:00
Irmen de Jong 8abdb837b2 fix long equality comparisons 2025-10-04 17:21:39 +02:00
Irmen de Jong cdeabd4b66 only * and / 2025-10-04 05:13:18 +02:00
Irmen de Jong efff74c0f1 added sys.pushl() and sys.popl() 2025-10-04 03:05:50 +02:00
Irmen de Jong 0e177cb531 added cbm.RDTIML and cbm.SETTIML wrappers 2025-10-04 02:00:24 +02:00
Irmen de Jong 35b921b062 added bcd module for decimal addition and subtraction using BCD mode 2025-10-03 23:36:17 +02:00
Irmen de Jong a7d98e43b8 cmp(long, long) 2025-10-03 22:41:59 +02:00
Irmen de Jong 845ee2dd83 smaller code for several long operations 2025-10-03 21:25:13 +02:00
Irmen de Jong f9b0bfe31b implement rol() and ror() on longs (also rol2 and ror2 and abs) 2025-10-03 01:22:24 +02:00
Irmen de Jong f303d15628 fix long equality checks 2025-10-03 01:22:24 +02:00
Irmen de Jong 67f7ffa52d multi value returns with longs 2025-10-03 01:22:24 +02:00
Irmen de Jong 88c5d9783a long params and return values 2025-10-03 01:22:24 +02:00
Irmen de Jong e7fc0360ad long bitwise operator expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong cd1862dd9f fix signed long shift right 2025-10-03 01:22:24 +02:00
Irmen de Jong 045c4909a8 long equality comparisons 2025-10-03 01:22:24 +02:00
Irmen de Jong f1bfe619b2 avoiding endless loop in optimizer 2025-10-03 01:22:24 +02:00
Irmen de Jong e0107bacbd implement long << >> expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong b3bd2a6a09 fixed a bunch of long type handling 2025-10-03 01:22:24 +02:00
Irmen de Jong ff1f58e022 implement simple + / - long expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong 557b12668d implement print_ulhex(long, prefix) 2025-10-03 01:22:24 +02:00
Irmen de Jong b058f1c7c2 implement mklong(a,b,c,d) and mklong2(w1,w2) 2025-10-03 01:22:24 +02:00
Irmen de Jong 3e07b6ca70 adding long arrays 2025-10-03 01:22:24 +02:00
Irmen de Jong d66dc664de work on longs 2025-10-03 01:22:24 +02:00
Irmen de Jong a2b9d78cf3 start with introducing LONG datatype (32 bits signed integer) 2025-10-03 01:22:24 +02:00
Irmen de Jong 44f70da113 upgrade to JDK 17 for the build process (required by Gradle 9) 2025-10-03 00:18:55 +02:00
Irmen de Jong c87eaf576d buildfix and update to Gradle 9.1.0 2025-10-03 00:11:52 +02:00
Irmen de Jong c09b1395f6 IR: add a unit test for the SSA basic block change 2025-10-01 22:49:51 +02:00
Irmen de Jong d6a8201291 IR: fix SQRT reg1 type 2025-10-01 20:28:38 +02:00
Irmen de Jong 4187f97f7a IR: rename <CODE> to <CHUNK> , put the code lines in a new sub node <CODE> for improved xml structure 2025-10-01 19:32:05 +02:00
Irmen de Jong 9d3b2f12fd IR code blocks now better SSA basic blocks (ending with single branch instruction) 2025-10-01 19:08:44 +02:00
Irmen de Jong 87c1bbbf40 readme about LSP 2025-10-01 01:15:00 +02:00
Irmen de Jong a7ad6abdb9 Merge branch 'languageServer'
# Conflicts:
#	examples/test.p8
2025-09-30 22:14:03 +02:00
Irmen de Jong a611406020 IR: add compilerversion attribute to p8ir file 2025-09-30 22:07:04 +02:00
Irmen de Jong 75da38224d IR: make LSIG,MSIG,CONCAT instruction set flags to skip cmp #0 afterwards (if msb(x)>0) 2025-09-30 21:48:57 +02:00
Irmen de Jong 8d6f3301c8 conv.any2uword() return values have been changed to be more useful and convenient
now returns both the actual value and the number of characters, and the return values for the virtual target now matches the others.
2025-09-30 20:44:02 +02:00
Irmen de Jong 86b52a1c5e fix endless loop in rewriting type of const long values 2025-09-29 22:28:13 +02:00
Irmen de Jong 2c8b1c2022 moved cx16.cpu_is_65816() to sys.cpu_is_65816(). It know also does proper detection on the C64 and C128 like on the X16, because those two computers can also have this CPU via a SuperCPU expansion. 2025-09-29 21:37:26 +02:00
Irmen de Jong ab1f065752 first setup of LSP languageserver 2025-09-27 15:27:37 +02:00
Irmen de Jong 2c7256a443 support assignment to indexed pointer targets 2025-09-27 14:52:04 +02:00
Irmen de Jong 97420b28e5 preparing to support assignment to indexed pointer targets 2025-09-27 11:03:55 +02:00
Irmen de Jong 1467c7039d fix pointer deref ast printer 2025-09-27 10:42:55 +02:00
Irmen de Jong d319badc6c default 0 for long type 2025-09-25 21:32:23 +02:00
Irmen de Jong 65d6c1c438 fix IR instruction R/W usage counter 2025-09-25 01:50:27 +02:00
Irmen de Jong abeefb5655 improved pointer[0] 2025-09-24 22:26:09 +02:00
Irmen de Jong 50fecbcebe c64 sprite multiplexer WIP 2025-09-24 21:11:08 +02:00
Irmen de Jong 4fe8b72d42 fix broken uword comparison and asm peephole optimization 2025-09-24 02:42:26 +02:00
Irmen de Jong f3b060df51 no longer save Y register when loading value through ZP pointer LDA (zp),Y 2025-09-23 22:59:01 +02:00
Irmen de Jong 09d1cb6925 fix crash when indexing on a label or subroutine name
working on multiplexer
2025-09-23 20:07:52 +02:00
Irmen de Jong 54fa72fa98 added sys.waitrasterline() routine like sys.waitvsync() but wait for a given raster line
optimize uword <= $xx00 into msb(uword)<$xx
2025-09-23 01:09:54 +02:00
Irmen de Jong fd62fe7511 fix crash on invalid type cast added to ptr deref expression ('.' operator) 2025-09-22 23:54:01 +02:00
Irmen de Jong bfb34dff62 fix recursive var decl error in case of some pointer derefs 2025-09-22 23:45:59 +02:00
Irmen de Jong 817b623596 optimize uword >= $xx00 into msb(uword)>=$xx 2025-09-22 22:59:56 +02:00
Irmen de Jong f6dbeb1f63 working on a more complex c64 sprite multiplexer 2025-09-22 21:58:12 +02:00
Irmen de Jong c4b9bdd33f proper pointer array initializer size checking, nicer sprite movement in simplemultiplexer 2025-09-22 20:07:38 +02:00
Irmen de Jong 68e0d5f1b5 add sys.set_rasterline on c64 and c128 targets as well (cx16 already had it) 2025-09-21 01:36:43 +02:00
Irmen de Jong 4939e3df55 add sys.update_rasterirq, more robust sei/cli handling, added simple c64 sprite multiplexer example 2025-09-21 00:28:33 +02:00
Irmen de Jong 19f19f3880 doc and opening borders 2025-09-20 21:44:48 +02:00
Irmen de Jong ad0c767ea8 %breakpoint! introduced to place after an assignment to make it parse correctly 2025-09-20 00:46:15 +02:00
Irmen de Jong ed5f4d5855 fix missing subroutine argument list check 2025-09-19 05:35:03 +02:00
Irmen de Jong c2f5d37486 new cx16/charfade.p8 example 2025-09-19 04:44:08 +02:00
Irmen de Jong 231b50dacb optimize certain word multiplication with bit shifting if it's a power of 2 2025-09-18 20:57:26 +02:00
Irmen de Jong a71895cbe8 optimize pointer.field += 1 into pointer.field INC/DEC 2025-09-18 19:27:36 +02:00
Irmen de Jong a8bede17b2 fix defer() with the arena allocator ("return values are evaluated before the defer is executed") 2025-09-17 23:59:32 +02:00
Irmen de Jong f6c8e693a5 add offsetof() 2025-09-17 23:30:15 +02:00
Irmen de Jong 9461e4088c fixed the hashtable example and workarounds for misbehaving defer in allocators 2025-09-17 22:53:01 +02:00
Irmen de Jong efd73fd10d implement some more in place pointer operators 2025-09-17 18:19:03 +02:00
Irmen de Jong ddf6e84a1a fix type checks for wrong pointer types in pointer array initalizer and assignment 2025-09-16 21:18:29 +02:00
Irmen de Jong 633d6c34e2 add support for struct pointers and short-form initializers in arrays and assignments 2025-09-16 20:37:50 +02:00
Irmen de Jong 6e7fbc6683 fix crash when invalid struct name is used in pointer decl 2025-09-15 20:39:21 +02:00
Irmen de Jong 124ea1230b fix vm to understand struct instances in arrays 2025-09-14 19:04:01 +02:00
Irmen de Jong 8b48a295b6 allow struct initializers to occur in array literals 2025-09-14 18:16:46 +02:00
Irmen de Jong d285d37fdb fix clobbers syntax in symboldumper 2025-09-13 21:47:52 +02:00
Irmen de Jong 8bb927b483 fix compiler crash with on..call statement in nested scope 2025-09-13 06:44:49 +02:00
Irmen de Jong 1af4cd0d63 fix struct initializer error checking 2025-09-12 19:55:12 +02:00
Irmen de Jong db2f28c4cd add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)
fix nullpointer in array initalizer
2025-09-12 17:09:44 +02:00
Irmen de Jong 5d9fbd2ccc fix doc build 2025-09-11 22:53:57 +02:00
Irmen de Jong 7efc709538 release 12.0 BETA 1 2025-09-11 22:50:34 +02:00
Irmen de Jong 79419a98d0 add if-expression versions for the conditionals if_cc, if_cs, if_vc etc 2025-09-11 01:57:30 +02:00
Irmen de Jong 1c77d5d5e7 avoid creating inferredtype instances all the time 2025-09-10 20:43:15 +02:00
Irmen de Jong c6854e22a3 fix wrong operand order for in place pointer subtraction 2025-09-10 17:19:03 +02:00
Irmen de Jong 83acc2f285 fix pointer variable overwriting when used in expression 2025-09-10 16:40:45 +02:00
Irmen de Jong 6de95f7a3b new static struct initializer syntax ^^Node : [1,2,3]
avoids confusion with function calls
2025-09-10 13:47:12 +02:00
Irmen de Jong 82839a2d82 fix alias error and byte to word assignment error 2025-09-09 15:05:02 +02:00
Irmen de Jong e5fc9b3132 fix 6502 pointer arithmetic optimization that led to increased code size 2025-09-09 14:06:41 +02:00
Irmen de Jong ced4c5944a fix broken optimization for wordvar - value expressions 2025-09-09 08:05:51 +02:00
Irmen de Jong d4c460072b don't place library and subroutine parameter pointers in ZP automatically, to save ZP space 2025-09-09 06:25:40 +02:00
Irmen de Jong 0b9384b556 fix typecasted byte-to-word pointer assignment, some more asm optimizations 2025-09-09 05:56:55 +02:00
Irmen de Jong 6c3277e3e3 fix more alias bugs 2025-09-09 02:28:05 +02:00
Irmen de Jong d9ff1eb38a fix internal error in on..call 2025-09-08 19:30:32 +02:00
Irmen de Jong e178097735 support more struct instance assignments via memcopy() 2025-09-08 13:18:50 +02:00
Irmen de Jong a1ab8ed208 fixing alias bugs 2025-09-07 22:52:13 +02:00
Irmen de Jong 6ababbf8f4 support more struct instance assignments via memcopy() 2025-09-07 00:29:18 +02:00
Irmen de Jong 79629befc1 fix pointer arithmetic error in struct instance assignments (that get turned into a memcopy call) 2025-09-06 00:28:59 +02:00
Irmen de Jong 8022c0772a oops 2025-09-05 20:37:19 +02:00
Irmen de Jong 8ad2b4638b fix and optimize nodes[i].field (it could clobber the actual temporary pointer value in a scratch register) 2025-09-05 20:02:37 +02:00
Irmen de Jong 3a0392df8a there's a problem with the struct initializer syntax 2025-09-05 00:18:42 +02:00
Irmen de Jong beb28b061d fix non-struct pointer deref code bug
fix gradle 9 compatibility issue (fixes #175)
2025-09-04 21:30:20 +02:00
Irmen de Jong c39acc5031 fix mistakes 2025-09-04 00:30:00 +02:00
Irmen de Jong f54a29c415 small 6502 pointer arithmetic expression rewrite that produces smaller code 2025-09-03 23:54:10 +02:00
Irmen de Jong 783b111059 pointer to uword casts should not be removed
optimized addUnsignedByteOrWordToAY a tiny bit (but needs more)
2025-09-03 23:09:11 +02:00
Irmen de Jong fb286f8b54 optimize operatorDereference() to not always use a zp scratch pointer 2025-09-03 20:30:10 +02:00
Irmen de Jong 5999110e3f avoid using temporary pointer to access struct fields if a simple Y indexed cpu instruction can be used 2025-09-03 18:37:56 +02:00
Irmen de Jong 52a757ea78 todo 2025-09-02 00:42:46 +02:00
Irmen de Jong 28df08eea8 introduce P8ZP_SCRATCH_PTR temporary zp pointer to avoid clobbering W1,W2 2025-09-01 23:56:31 +02:00
Irmen de Jong 79505308ba optimize indexed pointer code a bit more 2025-09-01 22:18:17 +02:00
Irmen de Jong a7e9d8e14b fix struct field offset calculations 2025-09-01 18:57:02 +02:00
Irmen de Jong 08f3abe5bf prefer to put pointer variables into zeropage (add implicit @zp) 2025-08-31 17:06:14 +02:00
Irmen de Jong 3ef09d7d9a use LOADFIELD instruction more instead of an extra explicit ADD
add implicit @zp to pointer variables if they don't have a preference
2025-08-31 15:53:44 +02:00
Irmen de Jong a9142b9ce5 slightly optimize certain pointer indexing calculation, fix invalid deref optimization 2025-08-31 13:40:28 +02:00
Irmen de Jong 13e6f64d3b implement missing struct pointer indexing codegen 2025-08-31 12:20:38 +02:00
Irmen de Jong 4a1256c772 optimizing some pointer codegen for indexed derefs 2025-08-30 21:20:14 +02:00
Irmen de Jong ff9bec90ec fix struct pointer array indexing field dereferencing
the pointers/sorting example now actually works on the 6502 too
2025-08-30 12:24:30 +02:00
Irmen de Jong a6fee1e510 fixing deref() 2025-08-30 07:04:01 +02:00
Irmen de Jong 80538f101e Merge branch 'master' into structs6502 2025-08-29 21:42:03 +02:00
Irmen de Jong aee53b14c7 avoid costly multiplications to get indexed pointer address 2025-08-29 20:18:26 +02:00
Irmen de Jong 5eb2fc8d86 fix a bad pointer arithmetic optimization 2025-08-29 19:36:57 +02:00
Irmen de Jong 98f91bbf88 another attempt to get correct docs chapters 2025-08-29 02:50:09 +02:00
Irmen de Jong b48b36ef18 another attempt to get correct docs chapters 2025-08-29 00:52:29 +02:00
Irmen de Jong 221a093e5f optimize some pointer arithmetic, fix pointer arithmetic for ptr-value 2025-08-28 23:08:06 +02:00
Irmen de Jong 2ca1820d4e Merge branch 'master' into structs6502 2025-08-28 21:55:46 +02:00
Irmen de Jong d58737d5be use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:46:09 +02:00
Irmen de Jong b52cee3154 use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:19:16 +02:00
Irmen de Jong 9a76941e10 fix invalid optimization for ptr-value 2025-08-28 20:13:02 +02:00
Irmen de Jong 0285a4cce1 avoid needless pointer arithmetic multiplication by struct size 1 2025-08-27 21:50:09 +02:00
Irmen de Jong 5a3aa1bd25 optimized IR to return a constant value: use RETURNI 2025-08-26 22:54:11 +02:00
Irmen de Jong 0f79351de9 support word size indexing on typed pointers 2025-08-25 22:58:48 +02:00
Irmen de Jong 3cdb25ce8e fix invalid string comparisons being generated for regular pointer value comparison 2025-08-24 16:19:42 +02:00
Irmen de Jong b7193bd0c6 update examples to use typed pointers where appropriate
(found 2 regressions that still need to be handled)
2025-08-24 15:17:47 +02:00
Irmen de Jong 10ff6a0095 Merge branch 'master' into structs6502 2025-08-24 14:34:15 +02:00
Irmen de Jong d30f58004e converting untyped to typed pointers in custom target libraries 2025-08-24 14:27:25 +02:00
Irmen de Jong 17bdb22e4f converting untyped to typed pointers in c64,c128,pet32 libraries 2025-08-24 14:21:51 +02:00
Irmen de Jong a68209be37 converting untyped to typed pointers in cx16 libraries 2025-08-24 14:13:02 +02:00
Irmen de Jong 7b40eade44 added txt.rvs_on() and txt.rvs_off(), added txt.color() support for virtual target 2025-08-24 13:29:29 +02:00
Irmen de Jong 8a717c74b9 sorting lib symboldumps 2025-08-24 13:07:10 +02:00
Irmen de Jong aa72ded21e add sorting module to symboldump, moved shared cbm textio routines to shared module 2025-08-24 13:05:38 +02:00
Irmen de Jong 8e53c83844 optimize certain ptr+value expression on 6502 2025-08-24 05:46:49 +02:00
Irmen de Jong e2a2db1256 converting untyped to typed pointers in libraries 2025-08-22 22:47:19 +02:00
markjreed 2afd2d4dae Make PET PLOT routine more efficient (#174)
* feat: take advantage of KERNAL vars to implement PLOT

* fix: tabs

* fix: update TBLX too, so cursor change sticks after output

* fix: formatting
2025-08-22 21:28:21 +02:00
Irmen de Jong fad17cc094 support a few other simple cases of struct instance assignment 2025-08-21 00:04:30 +02:00
Irmen de Jong f93d957999 proper warning for struct instance assignment (not yet supported) 2025-08-20 20:35:29 +02:00
Irmen de Jong 8db0344cee Merge branch 'master' into structs6502
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
2025-08-19 23:10:15 +02:00
Irmen de Jong 32e531f951 test 2025-08-19 23:09:33 +02:00
Irmen de Jong a4769702f9 todo 2025-08-19 23:06:04 +02:00
Irmen de Jong cf19fb8df1 fix 6502 struct type and field name prefixing and datatype references to stale structs from stale Symboltable 2025-08-19 20:47:37 +02:00
Irmen de Jong 79b8bb5c9f Merge branch 'master' into structs6502 2025-08-19 01:12:50 +02:00
Irmen de Jong fc5889ec0b kotlin 2.2.10, kotest 2025-08-19 01:04:24 +02:00
Irmen de Jong 369303c46d fix register clobbering in pointer deref 2025-08-18 23:36:35 +02:00
Irmen de Jong d65670cc7b fix 6502 address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 20:24:39 +02:00
Irmen de Jong f74eeaee0f fix IR address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 03:24:32 +02:00
Irmen de Jong 826fb3e9c2 implemented assign indexed ptr byte 2025-08-17 13:21:31 +02:00
Irmen de Jong a3d7b8a899 fix detection of string comparisons (make it aware of new pointer types equivalent to STR) 2025-08-16 17:04:59 +02:00
Irmen de Jong 0cc36ed6e4 pointer TODO's all as stub methods in the PointerAssignmentsGen class 2025-08-16 16:21:06 +02:00
Irmen de Jong 976bd52972 docs dark mode 2025-08-16 11:32:24 +02:00
Irmen de Jong 4a8d5def84 code cleanups, pointer TODOs, docs dark mode 2025-08-16 11:25:18 +02:00
Irmen de Jong 2f60716082 6502 struct allocation to asm file, struct name and field prefixing (maybe unneeded...) 2025-08-16 01:49:22 +02:00
Irmen de Jong 729efb04e1 fix code size regressions 2025-08-14 23:59:59 +02:00
Irmen de Jong 4ea8b4d445 fix unknown field test and redundant errors 2025-08-14 21:01:25 +02:00
Irmen de Jong e800c165f9 fix 6502 inplace pointer variable assignment 2025-08-14 20:38:18 +02:00
Irmen de Jong fd9bd23449 6502 statementreorderer: str -> ^^ubyte 2025-08-13 18:45:29 +02:00
Irmen de Jong 8880ed1393 fix address-of struct fields 2025-08-08 23:08:47 +02:00
Irmen de Jong f7fde070ca fix boolean pointer condition in if expression 2025-08-08 22:15:58 +02:00
Irmen de Jong 5ada80779d Merge branch 'refs/heads/master' into structs6502
# Conflicts:
#	examples/test.p8
2025-08-07 21:25:07 +02:00
Irmen de Jong 8972235a0e fix missing peekbool() and pokebool() code gen 2025-08-07 21:22:48 +02:00
Irmen de Jong e56f533e38 more basic pointer inplace operations 2025-08-07 00:36:47 +02:00
Irmen de Jong 324fb7dbf7 more basic pointer inplace operations (float)
basic pointers unit test now passes
2025-08-06 22:26:23 +02:00
Irmen de Jong 44285b9b5d more basic pointer inplace operations 2025-08-06 00:32:15 +02:00
Irmen de Jong a68f477d61 Merge branch 'master' into structs6502
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-08-05 23:29:58 +02:00
Irmen de Jong ae9f99448e fix pointer subtract arithmetic 2025-08-05 23:23:29 +02:00
Irmen de Jong 7c0fb10197 fix bug: VM MULR float error, another pointer dependency checker error 2025-08-05 22:00:05 +02:00
Irmen de Jong 9e85571a7b fix pointer variable usage detection in other block 2025-08-05 17:39:22 +02:00
Irmen de Jong 9e10c15e2e working on 6502 pointer inplace assignments 2025-08-04 23:32:17 +02:00
Irmen de Jong 6bd7752bac working on 6502 pointer dereferencing 2025-08-04 20:22:13 +02:00
Irmen de Jong 83ec437e8a testpointers unit test now also for 6502 targets
implementing first simple pointer operations
pointer vars also allocated in ZP for dontcare
2025-08-03 22:12:03 +02:00
Irmen de Jong 4a1d05dd46 first 6502 codegen results 2025-08-03 16:10:00 +02:00
Irmen de Jong aa324e355a remove 6502 pointer check, TODOs for pointer assignments 2025-08-03 13:28:39 +02:00
Irmen de Jong 5cb8bcead7 todo 2025-08-03 00:54:43 +02:00
Irmen de Jong bbd06c0c99 implement peekbool/pokebool on 6502, fix float assignment register error 2025-08-02 21:38:29 +02:00
Irmen de Jong 651830ea82 update syntax files 2025-08-02 21:00:16 +02:00
Irmen de Jong c70146f1dc Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
2025-08-02 19:26:54 +02:00
Irmen de Jong d4e83b28bb error messages and trying to improve support for struct allocs in arrays
added sorting example
2025-08-02 19:22:58 +02:00
Irmen de Jong bc58a25765 allow sizeof(^^type) to return the size of a pointer 2025-08-02 11:33:15 +02:00
Irmen de Jong 38645022c9 actually disallow ^^str 2025-08-02 05:49:12 +02:00
Irmen de Jong 647cd0fbe1 fix pointer[i].field compiler crash 2025-08-02 05:29:16 +02:00
Irmen de Jong ea8935a346 doc 2025-08-02 00:02:29 +02:00
Irmen de Jong 7ea80babfc todo 2025-08-01 23:38:49 +02:00
Irmen de Jong dee761a99e fix compiler crash on certain pointer assignments 2025-08-01 22:22:43 +02:00
Irmen de Jong 88ee7a8187 fix expected outcome of function call arg type pointer test 2025-08-01 00:23:10 +02:00
Irmen de Jong eb8b408b82 fix countries[2]^^ = 0 compiler crash 2025-07-31 02:03:18 +02:00
Irmen de Jong 3d10882f57 fix ast printing 2025-07-30 23:42:45 +02:00
Irmen de Jong 1988496512 correct commit hash 2025-07-30 01:08:22 +02:00
Irmen de Jong 88b074c208 pointer types should just be uwords in IR 2025-07-30 01:07:37 +02:00
Irmen de Jong c4c5636a81 fixing array indexing on pointers 2025-07-29 23:41:38 +02:00
Irmen de Jong c39d570b72 make more use of ISubType interface itself rather than casting it to StructDecl all the time 2025-07-29 22:59:31 +02:00
Irmen de Jong 4ccd7f9f3a improve docs about recursion 2025-07-29 22:49:24 +02:00
Irmen de Jong 1c9c5aeef7 todos 2025-07-28 23:29:18 +02:00
Irmen de Jong 23ad540aa5 fix IR codegen type error on array pointer dereference 2025-07-28 20:25:56 +02:00
Irmen de Jong 08810c2749 proper error message for unsupported &&subroutine 2025-07-27 23:44:24 +02:00
Irmen de Jong a52966f327 rollback implicit casts to boolean in struct initializers and function call arguments 2025-07-27 03:27:26 +02:00
Irmen de Jong 624220e9a3 fix boolean struct field as if conditional 2025-07-27 01:58:18 +02:00
Irmen de Jong 842b11ed9e struct docs 2025-07-27 00:00:40 +02:00
Irmen de Jong 82267b3f56 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/codegeneration/TestVariables.kt
#	docs/source/_static/symboldumps/skeletons-c128.txt
#	docs/source/_static/symboldumps/skeletons-c64.txt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
#	docs/source/_static/symboldumps/skeletons-pet32.txt
#	docs/source/_static/symboldumps/skeletons-virtual.txt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-26 12:43:13 +02:00
Irmen de Jong 67fb45a55b don't produce invalid boolean initalization error. Fixes #173 2025-07-26 12:35:23 +02:00
Irmen de Jong 11186f1dbe make sure that the virtual target -emu (vm) only runs the actual .p8ir file (fixes #172)
added emudbg.console_nl()
2025-07-26 11:19:01 +02:00
Irmen de Jong 0116fac201 release 11.4.1 2025-07-24 23:00:10 +02:00
Irmen de Jong 817f4f8e7c Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
#	compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
#	compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-24 22:27:29 +02:00
Irmen de Jong 866313209b fixed zp vars 0 initialization 2025-07-24 00:17:31 +02:00
Irmen de Jong 28e351daab new library routine skeletons 2025-07-22 18:18:15 +02:00
Irmen de Jong 893e16d814 replace str or ubyte[] param and returnvalue type into ^^ubyte rather than uword 2025-07-21 22:59:14 +02:00
Irmen de Jong 33470c47fc last changes to virtual diskio to make untyped pointers typed 2025-07-21 22:13:36 +02:00
Irmen de Jong 63f7b87572 Merge branch 'master' into structs 2025-07-21 22:11:34 +02:00
Irmen de Jong e2901cca1b fix virtual diskio save_raw() 2025-07-21 22:10:50 +02:00
Irmen de Jong ce8006992a changing virtual diskio to use typed pointers 2025-07-21 22:10:04 +02:00
Irmen de Jong 0b5413ad83 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-07-21 20:53:06 +02:00
Irmen de Jong dd7adde387 fix virtual diskio.f_write 2025-07-21 20:52:11 +02:00
Irmen de Jong 23058b51a1 started changing libs to typed pointers 2025-07-21 20:50:33 +02:00
Irmen de Jong 2f90c53ad0 started changing libs to typed pointers 2025-07-20 23:59:59 +02:00
Irmen de Jong c3be7ab4b3 fix if expression type problems with pointers 2025-07-18 23:44:55 +02:00
Irmen de Jong a9b8fbc6c6 Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-07-18 22:37:36 +02:00
Irmen de Jong 720988ae72 proper warnings for using pure builtin functions as a statement (discarding the result)
swallow a defer warning for a very common use case
2025-07-18 22:37:07 +02:00
Irmen de Jong b0981a5fae pointers no longer implicitly converted to boolean in expressions, to be consistent with how integers are handled in conditionals
adding particles fountain examples
2025-07-18 01:43:35 +02:00
Irmen de Jong ea5deeefbd new links to Codebase64 website 2025-07-17 23:06:39 +02:00
Irmen de Jong 054c98da7c add link to extra prog8 compilation targets 2025-07-15 00:17:04 +02:00
Irmen de Jong dc434d034a fix address-of identifier alias replacement stripping array index away 2025-07-14 23:54:26 +02:00
Irmen de Jong a4a1b563aa sdks 2025-07-14 21:54:56 +02:00
Irmen de Jong 3f34b83e0d sdks 2025-07-14 21:54:40 +02:00
Irmen de Jong 9c63ef39c7 fix pointer test 2025-07-07 23:09:45 +02:00
Irmen de Jong 9f6106452e revert & to untyped pointer, added && for typed pointer address-of 2025-07-07 16:17:07 +02:00
Irmen de Jong f9fbfe30e3 fix &x +/- offset pointer arithmetic expression 2025-07-07 13:11:57 +02:00
Irmen de Jong 9a9bf170c6 Merge branch 'master' into structs 2025-07-06 23:17:00 +02:00
Irmen de Jong 7dd64b4f13 fix 'cpa' instruction generated in certain boolean assignment, must be 'cmp' 2025-07-06 22:43:19 +02:00
Irmen de Jong b6c0bac96f identified problems with pointer array as parameter 2025-07-06 20:33:20 +02:00
Irmen de Jong 8ede098154 fix pointer array initialization 2025-07-06 19:42:54 +02:00
Irmen de Jong 2a4a3b786e cleanup error message for currently unsupported deref'd pointer assignments 2025-07-06 14:59:42 +02:00
Irmen de Jong b4e0a2019e fixed assignment to a[i]^^ 2025-07-06 13:38:22 +02:00
Irmen de Jong e14c3f8b59 code cleanups 2025-07-06 00:52:37 +02:00
Irmen de Jong c81f76226d Merge branch 'master' into structs 2025-07-06 00:37:58 +02:00
Irmen de Jong edc353cc24 more kotlin 2.2 settings 2025-07-06 00:37:15 +02:00
Irmen de Jong dcce519c69 Merge branch 'master' into structs
# Conflicts:
#	.idea/libraries/KotlinJavaRuntime.xml
#	build.gradle.kts
#	gradle.properties
2025-07-06 00:07:16 +02:00
Irmen de Jong 0a16dcafc0 update kotlin and gradle wrapper 2025-07-06 00:01:24 +02:00
Irmen de Jong 54d41b7f6f fixed a[i]^^ 2025-07-05 23:54:08 +02:00
Irmen de Jong 0541b84d09 Merge branch 'master' into structs 2025-06-29 16:34:32 +02:00
gillham 1b420f7fe7 Add a preliminary external custom target for the Foenix F256 family of modern retro computers. (#171) 2025-06-29 11:14:34 +02:00
Irmen de Jong 6a9a82ff9d doc 2025-06-27 18:14:36 +02:00
markjreed aa36e6b19f flesh out C128-specific KERNAL calls (#170)
* flesh out C128-specific KERNAL calls

* fix: typo in comment

* fix: typo in comment

* fix: include return values of INDCMP

* fix: rearrange return values of INDCMP
2025-06-27 18:13:26 +02:00
Irmen de Jong 51cb6aad50 add c128.PRIMM() 2025-06-27 17:39:31 +02:00
Irmen de Jong b5ce409592 clarify booleans 2025-06-26 20:06:36 +02:00
Irmen de Jong 2119817e4a Merge branch 'master' into structs 2025-06-24 21:14:53 +02:00
Irmen de Jong 1efdfe8ea1 much nicer colors in the bubbleuniverse example 2025-06-23 21:25:05 +02:00
Irmen de Jong 67d4180825 lib 2025-06-20 22:01:29 +02:00
Irmen de Jong be31e190d2 shuffle arguments of 64tass command so the additional assembler options from custom targets actually work 2025-06-19 22:33:50 +02:00
Irmen de Jong a68cf3c812 fix animalgame node reuse 2025-06-18 23:34:47 +02:00
Irmen de Jong c2bf9024f8 start writing docs about structs and pointers, update syntax files with ^^ 2025-06-18 19:00:18 +02:00
Irmen de Jong bd72eaad4c Merge branch 'refs/heads/master' into structs
# Conflicts:
#	examples/test.p8
2025-06-18 17:53:20 +02:00
Irmen de Jong b5d1575823 added boolean typed versions of the cx16.r0-r15 virtual registers 2025-06-18 00:05:10 +02:00
Irmen de Jong 1d306e5cdc moved new animalgame 2025-06-17 23:21:54 +02:00
Irmen de Jong b137164fe6 allow str assigned to ^^ubyte without an explicit cast 2025-06-17 18:29:48 +02:00
Irmen de Jong 67d4ad50e1 add new animals example (that uses a pointer tree) 2025-06-17 01:08:36 +02:00
Irmen de Jong c71066af4c fixing name lookup issue 2025-06-16 22:15:51 +02:00
Irmen de Jong 6f0a0981bd fixing name lookup issue 2025-06-16 00:21:54 +02:00
Irmen de Jong 49a4d9ba37 allow str as struct field type (^^ubyte) and strings in struct initializers 2025-06-15 00:29:59 +02:00
Irmen de Jong fcdfa741b9 Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt
2025-06-14 21:07:23 +02:00
Irmen de Jong e3e395836d fix splitting of array decl and initializer for non numeric types 2025-06-13 23:31:56 +02:00
Irmen de Jong 3bab177d50 working on pointers/binarytree example 2025-06-13 23:20:15 +02:00
Irmen de Jong 12abafb917 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-06-12 00:11:58 +02:00
Irmen de Jong 8dc2e47507 fix partial unused code removal in vm target 2025-06-11 23:31:29 +02:00
Irmen de Jong 0be90dedf2 check for split word array as argument 2025-06-11 21:35:36 +02:00
Irmen de Jong daf7c3357c better detection of missing return statement
preparing 11.4
2025-06-09 16:01:56 +02:00
Irmen de Jong e6bab3ceeb IR: adding LOADFIELD and STOREFIELD instructions 2025-06-09 01:41:45 +02:00
Irmen de Jong 59387b2ae8 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-06-07 20:28:23 +02:00
Irmen de Jong e8795859c5 added sorting library for target virtual
added sorting routines that sort a values array together with the keys array
optimized gnomesort a little
2025-06-07 19:42:40 +02:00
Irmen de Jong bebe60b687 fix compiler crash on for x in wordvar, add sys.get_as_returnaddress() 2025-06-05 16:10:40 +02:00
Irmen de Jong ddceec364e optimized coroutines library 2025-06-04 21:34:32 +02:00
Irmen de Jong f8f20440d3 allow pointer to be treated as uword value in augmented assignments 2025-06-03 21:33:35 +02:00
Irmen de Jong f8722faa4e Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-06-03 21:15:19 +02:00
Irmen de Jong d067fa4b73 added strings.find_eol() 2025-06-03 21:09:44 +02:00
Irmen de Jong 26fbbf48a4 adapt new antlr visitor to the pointer/struct additions 2025-06-03 01:28:58 +02:00
Irmen de Jong d5cc414221 Merge branch 'master' into structs
# Conflicts:
#	codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-06-02 20:52:50 +02:00
Irmen de Jong b5e51ab937 cleaner timings output 2025-06-02 19:30:25 +02:00
Irmen de Jong 552e55c29f fix missing cmp #0 when asmsub call is part of a boolean expression 2025-06-02 19:22:00 +02:00
Irmen de Jong a228908c1a fix wrong address calculation for &wordarray[i] where i is a variable 2025-06-02 03:13:23 +02:00
Irmen de Jong 15fc3b6c04 replace old antlr2kotlin code with the new visitor-based translator 2025-06-02 01:56:07 +02:00
Irmen de Jong 0456badd02 creating on a new visitor-based antlr to kotlin translator 2025-06-02 01:18:07 +02:00
Irmen de Jong d28f154f1c Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-06-01 17:53:41 +02:00
Irmen de Jong 399cf5118d we will get a 11.4 version first before structs will land 2025-06-01 17:47:06 +02:00
Irmen de Jong a87f2640d3 fixed signed byte comparisons in case of overflowing values 2025-06-01 14:01:25 +02:00
Irmen de Jong a90ef274d7 fix word*128 codegen.
added cx16/landscape.p8 example that draws procedurally generated landscapes.
found bug in signed byte comparisons with overflow.
2025-05-31 05:27:19 +02:00
Irmen de Jong 1c02179c5c refactor loadIndexReg() 2025-05-30 21:39:17 +02:00
Irmen de Jong 77584493fd support a.b.ptr[i]^^.value as expression (RHS) 2025-05-30 20:28:10 +02:00
Irmen de Jong a36709e638 Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
2025-05-30 12:39:23 +02:00
Irmen de Jong 341778ba67 added -timings flag 2025-05-30 12:38:16 +02:00
Irmen de Jong 8d63cce749 working on deref after array indexing 2025-05-30 11:30:49 +02:00
Irmen de Jong ec50b5a007 homebrew info 2025-05-30 03:43:03 +02:00
Irmen de Jong 8e7bbcdbe0 clear syntax error for yet unsupported deref after array indexing 2025-05-29 21:14:38 +02:00
Irmen de Jong 37ecdc47b3 allow ptr1^^ = ptr^^ (replaces it with memcopy) 2025-05-29 16:34:47 +02:00
Irmen de Jong 112ca3cc53 allow sizeof(&thing), add sys.SIZEOF_POINTER 2025-05-29 15:58:29 +02:00
Irmen de Jong 33b3a1664c replace sizeof(list^^) with sizeof(List) to allow it to compile. Same with simple pointers. 2025-05-29 14:13:42 +02:00
Irmen de Jong 8a0c02e264 Merge branch 'refs/heads/master' into structs
# Conflicts:
#	codeCore/src/prog8/code/target/NormalMemSizer.kt
#	codeCore/src/prog8/code/target/VMTarget.kt
#	compiler/src/prog8/compiler/BuiltinFunctions.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-05-29 13:42:16 +02:00
Irmen de Jong 31d84c8921 doc 2025-05-29 13:35:20 +02:00
Irmen de Jong 34bedbeef1 optimize byte modulus (%) routine with repeated subtraction instead of using full division 2025-05-29 13:26:04 +02:00
Irmen de Jong 3b1b0985c1 make sizeof(float) work, so you don't have to use sys.SIZEOF_FLOAT anymore etc.
define sys.SIZEOF_FLOAT in terms of sizeof(float)
2025-05-29 12:38:03 +02:00
Irmen de Jong e40ace9dea todo 2025-05-29 01:35:53 +02:00
Irmen de Jong 4c0e6e2640 fix split/nosplit pointer arrays, replace ubyteptr^^ with @(ubyteptr), fix double dereference 2025-05-29 00:36:55 +02:00
Irmen de Jong 08b314c37d IR: fix various register type mismatches 2025-05-28 22:15:07 +02:00
Irmen de Jong 86da9d3c7e assigning to plain pointer with array indexing 2025-05-28 18:08:53 +02:00
Irmen de Jong 4e61e25c02 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
2025-05-27 23:52:59 +02:00
Irmen de Jong 5097d52d99 IR codegen for pointer indexing expressions, -assignment 2025-05-27 23:41:08 +02:00
Irmen de Jong 368387e1a7 allow floats to be (explicitly) cast to integers 2025-05-26 21:39:48 +02:00
Irmen de Jong 09d2185bb1 PtArrayIndexer variable is now nullable (because it could be a ptr deref instead) 2025-05-25 23:04:32 +02:00
Irmen de Jong 5c02e2bd71 fix a ptr indexing case, fix address-of fields 2025-05-25 21:32:31 +02:00
Irmen de Jong fb01389b3d cleaning up pointer deref 2025-05-25 18:33:37 +02:00
Irmen de Jong aaa81210ce cleaning up pointer indexing 2025-05-25 02:56:32 +02:00
Irmen de Jong 51269257ea fix a.b.c.d desugaring into pointer deref chain 2025-05-24 14:48:02 +02:00
Irmen de Jong 23a853db1e Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	examples/test.p8
2025-05-23 19:00:59 +02:00
Irmen de Jong 9da430ffeb vm: more complete V-flag handling. somd doc and todo updates. 2025-05-23 18:58:14 +02:00
Irmen de Jong cc063124cf add joystick control to cx16 fileselector.
fix fileselector Basic exasmple.
fixed too aggressive asm peephole optimization that destroyed %jumptable in libraries for example.
2025-05-23 17:50:11 +02:00
Irmen de Jong 3b37b89951 added cx16.joysticks_detect() and cx16.joysticks_getall() 2025-05-23 02:26:21 +02:00
Irmen de Jong 844b537d1e cobramk3 example now draws with new monogfx doublebuffering 2025-05-22 23:29:49 +02:00
Irmen de Jong caf1d4a22a fix monogfx INVERT draw mode 2025-05-22 21:29:23 +02:00
Irmen de Jong d8e244df99 fix monogfx example 2025-05-22 00:37:20 +02:00
Irmen de Jong 548e421e27 added doublebuffering to monogfx (in both lores and hires mode) 2025-05-22 00:10:03 +02:00
Irmen de Jong 322fa7ea69 slightly optimize monogfx plot() 2025-05-21 01:16:43 +02:00
Irmen de Jong db6c887795 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/ast/TestVariousCompilerAst.kt
2025-05-21 00:27:45 +02:00
Irmen de Jong cf7bea0985 cleanup RTS insertion and ast postprocessing before assembly generation 2025-05-21 00:19:50 +02:00
Irmen de Jong 61fe55168a Merge branch 'master' into structs
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
2025-05-20 22:35:04 +02:00
Irmen de Jong 25d7f8808f IR: added signed multiplication opcodes 2025-05-20 21:36:05 +02:00
Irmen de Jong 1c4999ec87 adding ptr unit tests 2025-05-20 20:57:05 +02:00
Irmen de Jong c726d3f937 fix ptr errors 2025-05-19 22:49:07 +02:00
Irmen de Jong f70341df1b fix ptr errors 2025-05-19 20:06:31 +02:00
Irmen de Jong f0b791452e fix many ptr deref errors 2025-05-19 01:33:57 +02:00
Irmen de Jong adf5600a9b simplify 2025-05-18 18:37:19 +02:00
Irmen de Jong 6d4ccc5feb fix pointer variable not getting marked as used in some cases 2025-05-18 13:23:56 +02:00
Irmen de Jong 5f3829d5cc partly fix weird errors for ptr indexed expressions 2025-05-17 22:56:41 +02:00
Irmen de Jong 770ebdcd4a party fix weird errors for ptr indexed assignment 2025-05-17 20:54:02 +02:00
Irmen de Jong 96f690e749 fix a ptr indexing error 2025-05-17 20:27:38 +02:00
Irmen de Jong eabdd3a8f3 fix the ptr.uword[index] assignment target error 2025-05-17 14:57:24 +02:00
Irmen de Jong 50650b966b repeat countervars again in zeropage if possible, fix pointer arithmetic error 2025-05-17 14:18:56 +02:00
Irmen de Jong 65e34d4989 stricter types for & operator (address-of), and fix invalid recursive subroutine flagging related to struct definition 2025-05-17 11:32:54 +02:00
Irmen de Jong 05dad5ab5f cleanup 6502 pointer usage checks 2025-05-16 21:44:28 +02:00
Irmen de Jong 1a69a2f1bc fix some ptr vs uword type checks 2025-05-16 20:31:15 +02:00
Irmen de Jong 435faafaad fix split-word storage (lsb/msb) of arrays of pointers 2025-05-16 17:53:15 +02:00
Irmen de Jong 686b32dc29 replace ^^str by ^^ubyte and allow returning ubyte/uword when pointer is expected 2025-05-16 17:53:15 +02:00
Irmen de Jong 0e64a22910 tweak address-of types 2025-05-16 17:53:15 +02:00
Irmen de Jong 4f0839f27e rewrite pointer[0] into @(pointer) if its ^^ubyte 2025-05-16 17:53:15 +02:00
Irmen de Jong bb1953267d Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-05-15 23:09:30 +02:00
Irmen de Jong acc630972a make keyboardhandler example restartable 2025-05-15 23:07:54 +02:00
Irmen de Jong 6a33be3fd8 IR: allow returning boolean in Pc/Pv cpu status register (Pz and Pn are not yet possible) 2025-05-15 22:56:45 +02:00
Irmen de Jong cd8aae4681 allow @(..) to take a ^^ubyte address pointer, not only uwords. 2025-05-15 22:12:42 +02:00
Irmen de Jong 11456496bd Merge branch 'master' into structs
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-15 21:35:31 +02:00
Irmen de Jong f5fc4e345c fix build error on case-insensitive filesystems 2025-05-15 21:11:34 +02:00
Irmen de Jong 86eef7039f @(..) now also accepts pointer to ubyte address 2025-05-15 20:07:02 +02:00
Irmen de Jong f4b2264fcf fix struct type checks for subroutine call arguments 2025-05-14 23:33:55 +02:00
Irmen de Jong 9b36ae2277 implement inplace boolean short-circuit operators on pointer dereferenced booleans 2025-05-14 21:29:50 +02:00
Irmen de Jong 913ab03963 get rid of invalid ARRAY_STRUCT data type (arrays of struct instance are not yet supported) 2025-05-14 20:43:00 +02:00
Irmen de Jong 38448e471c Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
#	examples/test.p8
2025-05-13 23:42:13 +02:00
Irmen de Jong 67231af623 fix forloop codegen over non-split word arrays of length >= 64 elements 2025-05-13 23:32:26 +02:00
Irmen de Jong e31ef6f06f IR: fix temp register type in for x in array 2025-05-13 22:23:04 +02:00
Irmen de Jong 09d188106a different temp var mechanism for certain array expression, remove old tmpvar mechanism. 2025-05-13 21:12:10 +02:00
Irmen de Jong d8e2116481 different temp var mechanism for for loops, and pokef() 2025-05-13 21:06:33 +02:00
Irmen de Jong 435dfbb932 optimize: rewrite suitable when into on..goto 2025-05-13 01:12:58 +02:00
Irmen de Jong ba93966474 optimize codegen: shortcut redundant jumps in when statement 2025-05-13 00:35:22 +02:00
Irmen de Jong ea8d17cdb2 optimized the cx16 multi-irq dispatcher used in cx16.enable_irq_handlers() 2025-05-12 23:26:54 +02:00
Irmen de Jong 082265fb25 todo 2025-05-12 00:24:57 +02:00
Irmen de Jong d138a7a567 add struct and pointers to IDEA syntax 2025-05-11 23:35:53 +02:00
Irmen de Jong ea27d732ab Merge branch 'refs/heads/master' into structs 2025-05-11 23:35:13 +02:00
Irmen de Jong 9e557ce8ac add keyword 'on' to IDEA syntax 2025-05-11 23:32:54 +02:00
Irmen de Jong 924e28e9b3 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt
#	compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/ast/walk/AstWalker.kt
#	compilerAst/src/prog8/ast/walk/IAstVisitor.kt
#	docs/source/todo.rst
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-11 23:23:06 +02:00
Irmen de Jong e5d9af75de remove double bra/jmp 2025-05-11 23:01:13 +02:00
Irmen de Jong 31c1bf8bc5 added on..goto/call statement 2025-05-11 21:37:44 +02:00
Irmen de Jong 37d4055036 translate newline '\n' to char code 13 in various encodings such as ISO (used to be 10)
This means that when printed, such newlines will now properly go to the next line in these encodings too (ISO variants, KATAKANA).
2025-05-11 19:45:24 +02:00
Irmen de Jong 60a73248cd todo 2025-05-11 03:16:48 +02:00
Irmen de Jong abbb7d7ba3 fix struct pointers in subroutine parameters and return values 2025-05-11 02:08:59 +02:00
Irmen de Jong 59c378089e fix some struct type and symbol lookup errors 2025-05-11 00:52:35 +02:00
Irmen de Jong 0b789b5f0b added most inplace operators for pointer deref 2025-05-10 20:58:01 +02:00
Irmen de Jong 4382b96a9a tweaking pointer deref in IR 2025-05-10 19:52:06 +02:00
Irmen de Jong 246e4f35a6 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	compiler/test/ast/TestConst.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-05-10 16:36:16 +02:00
Irmen de Jong 506062c6b6 start implementing ptr deref augmented assigns 2025-05-09 23:05:27 +02:00
Irmen de Jong 8353c689ca start making '&' (address-of) return a typed pointer, fixes some errors 2025-05-08 23:40:41 +02:00
Irmen de Jong e98e6f70ac Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-05-07 23:26:21 +02:00
Irmen de Jong 134352ed7c Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-07 22:32:41 +02:00
Irmen de Jong 5de626aab8 support comparison operators on pointers 2025-05-06 22:26:27 +02:00
Irmen de Jong 7aad5d486e Merge pull request #168 from adiee5/master
Update `nano` syntax highlighting
2025-05-06 18:17:58 +02:00
Irmen de Jong 701f155951 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt
#	compiler/test/TestSymbolTable.kt
#	docs/source/todo.rst
#	examples/test.p8
#	intermediate/src/prog8/intermediate/IRFileReader.kt
#	intermediate/src/prog8/intermediate/IRFileWriter.kt
#	intermediate/src/prog8/intermediate/IRSymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTableMaker.kt
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-06 17:59:36 +02:00
Irmen de Jong 8c324d7514 tweak error 2025-05-06 17:50:53 +02:00
adiee5 97390db5f5 Update prog8.nanorc 2025-05-05 21:06:56 +02:00
Irmen de Jong af920d1427 pointer arithmetic for '-', fixed '+' 2025-05-05 21:06:49 +02:00
Irmen de Jong 779ebc0537 pointer arithmetic for '+' 2025-05-05 18:09:19 +02:00
Irmen de Jong 38949b82c3 type check tuning 2025-05-05 15:41:32 +02:00
Irmen de Jong d11386ef26 type check tuning 2025-05-04 23:23:21 +02:00
Irmen de Jong 0e0377d1f0 IR/VM implemented struct allocations and initialization 2025-05-04 14:04:44 +02:00
Irmen de Jong 55e0dbab27 preparing for statically allocating struct instances 2025-05-03 23:44:29 +02:00
Irmen de Jong 4dc82f2c83 preparing for statically allocating struct instances 2025-05-03 19:00:27 +02:00
Irmen de Jong 1ba5587404 allow syntax for declaring variables with struct instance type 2025-05-03 16:14:22 +02:00
Irmen de Jong 835c4b6da3 allow multi-field declarations in structs, get rid of . -> ^^ rewrite 2025-05-03 12:32:29 +02:00
Irmen de Jong dbd955b61e tweak typecheck and better code for constant indexes in pointer derefs 2025-05-02 23:44:26 +02:00
Irmen de Jong d20e2fd88c structs will be a new major version of the compiler 2025-05-02 22:25:26 +02:00
Irmen de Jong e0dea89477 added support for ptr[x].field 2025-05-02 22:16:20 +02:00
Irmen de Jong 6fc2902895 fixing ptr traversal typecheck issues 2025-05-02 00:41:42 +02:00
Irmen de Jong c96e4b40d4 building syntax support for ptr[x].field
attempting to do this by making '.' an expression operator
2025-05-02 00:41:42 +02:00
Irmen de Jong 37da3e2170 parser 2025-05-02 00:41:42 +02:00
Irmen de Jong 2661d3c489 allow array syntax on pointers 2025-05-02 00:41:42 +02:00
Irmen de Jong b89bbb9281 allow pointers in subroutines params and return values 2025-05-02 00:41:42 +02:00
Irmen de Jong 696bf636ed better parsing of directive names results in better error messages when an invalid one is found 2025-05-02 00:41:42 +02:00
Irmen de Jong 40952a788a PtSub: params and returns now as children (in PtSignature node) for easier Ast walking
PtPointerDeref: same but for its start identifier
2025-05-02 00:41:42 +02:00
Irmen de Jong 0162e7a0c1 fix the scoping problems on subtypes 2025-05-02 00:41:42 +02:00
Irmen de Jong 6ce099f176 IR: fix ptr type checks and struct field assignment errors 2025-05-02 00:41:42 +02:00
Irmen de Jong 476a4bac8e IR: LOADI allows r1 and r2 to be the same for pointer chain dereference optimalization 2025-05-02 00:41:42 +02:00
Irmen de Jong 63a410a6df implicit cast to bool for numeric or pointers as condition arguments (to if, while, until) 2025-05-02 00:41:42 +02:00
Irmen de Jong cca27faa3b fix pointer value assignment (not dereferencing the actual pointer) 2025-05-02 00:41:42 +02:00
Irmen de Jong 803e6bd81a fix uword vs pointer type errors and casts 2025-05-02 00:41:42 +02:00
Irmen de Jong 88269628a2 had to turn ^type syntax into ^^type to avoid confusion with the eor operator once again 2025-05-02 00:41:42 +02:00
Irmen de Jong b920d553a0 make address-of dereference work 2025-05-02 00:41:42 +02:00
Irmen de Jong 5e2d0d0dfc fix param order of AssignTarget 2025-05-02 00:41:42 +02:00
Irmen de Jong 2ae3bd68eb more pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
Irmen de Jong 9c183f27eb pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
Irmen de Jong 8046023e82 pointer dereferencing for simple types (read and write) 2025-05-02 00:41:42 +02:00
Irmen de Jong e328520588 initial struct and typed pointer support 2025-05-02 00:41:40 +02:00
323 changed files with 27494 additions and 6369 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# An .aiignore file follows the same syntax as a .gitignore file.
# .gitignore documentation: https://git-scm.com/docs/gitignore
# you can ignore files
.DS_Store
*.log
*.tmp
*.bin
*.prg
# or folders
dist/
build/
out/
output/
.gradle/
.kotlin/
+2 -2
View File
@@ -20,10 +20,10 @@ jobs:
make -j4
sudo make install
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: temurin
- name: Build and test with Gradle
+3
View File
@@ -7,10 +7,13 @@ build/
dist/
output/
out/
out-new/
out-old/
.*cache/
*.directory
*.prg
*.bin
*.p8ir
*.labels.txt
*.vm.txt
*.vice-mon-list
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -7,13 +7,13 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards" />
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
<option name="version" value="2.2.0" />
</component>
</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:2.1.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-sources.jar!/" />
</SOURCES>
</library>
</component>
+2 -2
View File
@@ -4,8 +4,8 @@
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
-20
View File
@@ -1,20 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<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.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/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 />
</library>
</component>
+7 -7
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1.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$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1-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$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-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.1/kotlin-result-jvm-2.0.1-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$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
+4
View File
@@ -12,6 +12,7 @@
<option name="pkg" value="" />
<option name="language" value="Java" />
<option name="generateListener" value="false" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>
@@ -25,4 +26,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
+1
View File
@@ -15,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
+3 -1
View File
@@ -61,6 +61,7 @@ What does Prog8 provide?
- 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)
- Structs and typed pointers
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- tight control over Zeropage usage
@@ -71,6 +72,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
@@ -95,7 +97,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "pet32": Commodore PET (limited support)
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64OS, ...
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
+234
View File
@@ -0,0 +1,234 @@
; Binary Search Tree.
; It's a simple implementation for test/demonstration purposes of the pointer support;
; no balancing is done and memory is not freed when elements are removed.
%import textio
btree {
sub benchmark(uword max_time) -> uword {
txt.nl()
cbm.SETTIM(0,0,0)
uword score
while cbm.RDTIM16() < max_time {
bench_operations()
txt.chrout('.')
score++
}
txt.nl()
return score
}
sub bench_operations() {
arena.freeall()
btree.root = 0
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
btree.add(cx16.r0)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
void btree.contains(203)
void btree.contains(204)
void btree.contains(605)
void btree.contains(606)
btree.remove(9999)
btree.remove(97)
btree.remove(187)
btree.remove(203)
btree.remove(275)
btree.remove(321)
btree.remove(520)
btree.remove(562)
btree.remove(606)
btree.remove(719)
btree.remove(794)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
}
struct Node {
^^Node left
^^Node right
uword value
}
^^Node root = 0
sub add(uword value) {
^^Node node = arena.alloc(sizeof(Node))
node.value = value
node.left = node.right = 0
if root==0
root=node
else {
^^Node parent = root
repeat {
if parent.value >= value {
if parent.left!=0
parent = parent.left
else {
parent.left = node
return
}
} else {
if parent.right!=0
parent = parent.right
else {
parent.right = node
return
}
}
}
}
}
sub contains(uword value) -> bool {
^^Node r = root
while r!=0 {
if r.value==value
return true
if r.value>value
r = r.left
else
r = r.right
}
return false
}
sub size() -> ubyte {
ubyte count
if root!=0
count_node(root)
return count
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
count_node(r.left)
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
count_node(r.right)
r = sys.popw()
}
}
}
sub remove(uword value) {
; note: we don't deallocate the memory from the node, for simplicity sake
^^Node n = root
^^Node parent = 0
while n!=0 {
if n.value==value {
if n.left==0
replacechild(parent, n, n.right)
else if n.right==0
replacechild(parent, n, n.left)
else {
; Both left & right subtrees are present.
; N = node to delete.
; Find N's successor S. (N's right subtree's minimum element)
; Attach N's left subtree to S.left (S doesn't have a left child)
; Attach N's right subtree to Parent in place of N.
^^Node successor = find_successor(n)
successor.left = n.left
replacechild(parent, n, n.right)
}
return
}
parent = n
if n.value>value
n = n.left
else
n = n.right
}
sub find_successor(^^Node p) -> ^^Node {
^^Node succ = p
p = p.right
while p!=0 {
succ = p
p = p.left
}
return succ
}
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
if p.left==child
p.left = newchild
else
p.right = newchild
}
}
sub process_tree_inorder() {
if root!=0
process_tree(root)
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
process_tree(r.left)
r = sys.popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
process_tree(r.right)
r = sys.popw()
}
}
}
sub process_tree_preorder() {
if root!=0
process_tree(root,0)
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
}
}
}
}
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}
+10 -9
View File
@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -67,10 +68,6 @@ main {
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++
@@ -79,13 +76,17 @@ main {
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
announce_benchmark("btree-struct-pointers")
benchmark_score[benchmark_number] = btree.benchmark(200)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
@@ -93,14 +94,14 @@ main {
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
txt.print("\n\ntotal score : ")
txt.print_uw(total_score)
txt.print(" (higher=better)\n")
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
+5 -1
View File
@@ -1,9 +1,11 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
kotlin("jvm") version "2.1.20"
kotlin("jvm") version "2.2.20"
}
allprojects {
@@ -18,6 +20,8 @@ allprojects {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
jvmTarget = JvmTarget.JVM_11
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
}
sourceSets.all {
languageSettings {
+1 -3
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -7,7 +5,7 @@ plugins {
dependencies {
// should have no dependencies to other modules
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {
+2 -1
View File
@@ -5,9 +5,10 @@ import java.nio.file.Path
import kotlin.io.path.absolute
// the automatically generated module where all string literals are interned to:
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
@@ -37,6 +37,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val returns: ReturnConvention = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
@@ -45,6 +46,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
@@ -61,6 +63,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val paramConv = when(val paramType = actualParamTypes[0]) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R0R1_32, false)
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
@@ -88,20 +91,23 @@ class FSignature(val pure: Boolean, // does it have side effects?
val BuiltinFunctions: Map<String, FSignature> = mapOf(
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns a status in the carry flag, but not a proper return value
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *BaseDataType.entries.toTypedArray())),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
@@ -115,26 +121,36 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD)),
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, null),
@@ -13,6 +13,7 @@ class CompilationOptions(val output: OutputType,
val noSysInit: Boolean,
val romable: Boolean,
val compTarget: ICompilationTarget,
val compilerVersion: String,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var memtopAddress: UInt,
+234 -58
View File
@@ -13,6 +13,9 @@ enum class BaseDataType {
STR, // pass by reference
ARRAY, // pass by reference, subtype is the element type
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
UNDEFINED;
@@ -26,6 +29,7 @@ enum class BaseDataType {
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
@@ -34,7 +38,8 @@ enum class BaseDataType {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
@@ -45,89 +50,170 @@ enum class BaseDataType {
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?) {
interface ISubType {
val scopedNameString: String
fun memsize(sizer: IMemSizer): Int
fun sameas(other: ISubType): Boolean
fun getFieldType(name: String): DataType?
}
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) {
init {
if(base.isArray) {
require(sub != null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
when {
base.isPointerArray -> {
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
else -> {
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
"sub and subtype can't both be set"
}
}
}
else if(base==BaseDataType.STR)
require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
else
require(sub == null) { "only string and array base types can have a subtype"}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
}
override fun hashCode(): Int = Objects.hash(base, sub)
override fun hashCode(): Int = Objects.hash(base, sub, subType)
fun setActualSubType(actualSubType: ISubType) {
subType = actualSubType
subTypeFromAntlr = null
}
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null)
val BYTE = DataType(BaseDataType.BYTE, null)
val UWORD = DataType(BaseDataType.UWORD, null)
val WORD = DataType(BaseDataType.WORD, null)
val LONG = DataType(BaseDataType.LONG, null)
val FLOAT = DataType(BaseDataType.FLOAT, null)
val BOOL = DataType(BaseDataType.BOOL, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null)
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null)
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType) = simpletypes.getValue(dt)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
DataType(BaseDataType.ARRAY, actualElementDt)
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid element dt $elementDt")
throw NoSuchElementException("invalid basic element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType = if(dt.isBasic)
DataType(BaseDataType.POINTER, dt.base, null)
else
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
}
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
if(base.isArray || base==BaseDataType.STR)
forDt(sub!!)
else
throw IllegalArgumentException("not an array")
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
if (isArray) {
if (msb || isSplitWordArray)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
}
if (subType != null)
return pointer(this)
return UWORD
}
}
fun dereference(): DataType {
require(isPointer || isUnsignedWord)
return when {
isUnsignedWord -> forDt(BaseDataType.UBYTE)
sub!=null -> forDt(sub)
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
else -> throw IllegalArgumentException("cannot dereference this pointer type")
}
}
override fun toString(): String = when(base) {
BaseDataType.ARRAY -> {
@@ -138,6 +224,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -148,6 +235,15 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
}
BaseDataType.STRUCT_INSTANCE -> {
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
}
else -> base.name.lowercase()
}
@@ -160,6 +256,30 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "str"
BaseDataType.POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}"
subType!=null -> "^^${subType!!.scopedNameString}"
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
else -> "?????"
}
}
BaseDataType.STRUCT_INSTANCE -> {
when {
sub!=null -> sub.name.lowercase()
subType!=null -> subType!!.scopedNameString
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
else -> "?????"
}
}
BaseDataType.ARRAY_POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}["
subType!=null -> "^^${subType!!.scopedNameString}["
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
else -> "????? ["
}
}
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
@@ -167,6 +287,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
@@ -187,18 +308,37 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
targetType.isPointer -> this.isUnsignedWord || this == targetType
else -> false
}
}
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
BaseDataType.ARRAY_POINTER -> false
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
// note: for pointer types, size() doesn't return the size of the pointer itself but the size of the thing it points to
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
memsizer.memorySize(sub)
} else if(subType!=null) {
subType!!.memsize(memsizer)
} else {
memsizer.memorySize(base)
}
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
val isUndefined = base == BaseDataType.UNDEFINED
val isByte = base.isByte
val isUnsignedByte = base == BaseDataType.UBYTE
@@ -208,28 +348,35 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isUnsignedWord = base == BaseDataType.UWORD
val isSignedWord = base == BaseDataType.WORD
val isInteger = base.isInteger
val isWordOrByteOrBool = base.isWordOrByteOrBool
val isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isBoolArray = base.isArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && sub == BaseDataType.WORD
val isFloatArray = base.isArray && sub == BaseDataType.FLOAT
val isPointer = base.isPointer
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
val isPointerToWord = base.isPointer && sub?.isWord==true
val isStructInstance = base.isStructInstance
val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && sub == BaseDataType.WORD
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
@@ -253,7 +400,9 @@ enum class RegisterOrPair {
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1_32, R2R3_32, R4R5_32, R6R7_32, R8R9_32, R10R11_32, R12R13_32, R14R15_32;
companion object {
val names by lazy { entries.map { it.toString()} }
@@ -266,6 +415,18 @@ enum class RegisterOrPair {
}
}
fun startregname() = when(this) {
R0R1_32 -> "r0"
R2R3_32 -> "r2"
R4R5_32 -> "r4"
R6R7_32 -> "r6"
R8R9_32 -> "r8"
R10R11_32 -> "r10"
R12R13_32 -> "r12"
R14R15_32 -> "r14"
else -> throw IllegalArgumentException("must be a combined virtual register $this")
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
@@ -280,10 +441,14 @@ enum class RegisterOrPair {
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, null -> ""
else -> throw kotlin.IllegalArgumentException("invalid register param type")
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
}
return listOf("cx16", name.lowercase()+suffix)
}
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
fun isLong() = this in combinedLongRegisters
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
@@ -319,6 +484,17 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val combinedLongRegisters = arrayOf(
RegisterOrPair.R0R1_32,
RegisterOrPair.R2R3_32,
RegisterOrPair.R4R5_32,
RegisterOrPair.R6R7_32,
RegisterOrPair.R8R9_32,
RegisterOrPair.R10R11_32,
RegisterOrPair.R12R13_32,
RegisterOrPair.R14R15_32
)
val CpuRegisters = arrayOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
@@ -27,7 +27,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: String?
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
@@ -23,8 +23,9 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register byte, must be B1+1
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word
abstract val SCRATCH_PTR : UInt // temp storage for a pointer
// the variables allocated into Zeropage.
@@ -38,7 +39,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u))
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
}
}
@@ -72,6 +73,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val size: Int =
when {
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isPointer -> options.compTarget.memorySize(datatype, null)
datatype.isString || datatype.isArray -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
@@ -122,6 +124,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
datatype.isNumericOrBool -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
datatype.isString -> VarAllocation(address, datatype, size)
datatype.isArray -> VarAllocation(address, datatype, size)
datatype.isPointer -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
}
}
+10 -7
View File
@@ -6,12 +6,15 @@ import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@@ -28,10 +31,10 @@ class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by N
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
+6 -3
View File
@@ -7,12 +7,15 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@@ -27,17 +27,18 @@ class ConfigFileTarget(
override val defaultOutputType: OutputType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: String?,
override val additionalAssemblerOptions: List<String>,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
val zpScratchW1: UInt,
val zpScratchW2: UInt,
val zpScratchPtr: UInt,
val virtualregistersStart: UInt,
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(8) {
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {
@@ -109,8 +110,6 @@ class ConfigFileTarget(
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val assemblerOptions = assemblerOptionsStr.ifBlank { null }
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
@@ -128,12 +127,13 @@ class ConfigFileTarget(
defaultOutputType,
libraryPath,
customLauncher,
assemblerOptions,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),
props.getInteger("zp_scratch_w1"),
props.getInteger("zp_scratch_w2"),
props.getInteger("zp_scratch_ptr"),
props.getInteger("virtual_registers"),
zpFullsafe,
zpKernalsafe,
@@ -161,7 +161,7 @@ class ConfigFileTarget(
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = ConfigurableZeropage(
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2,
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr,
virtualregistersStart,
zpBasicsafe,
zpKernalsafe,
+6 -3
View File
@@ -6,12 +6,15 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
+1 -1
View File
@@ -18,7 +18,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
@@ -7,11 +7,14 @@ import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
@@ -25,7 +28,9 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isLong -> 4 * (numElements ?: 1)
dt.isPointer -> 2 // pointer is just a uword
dt.isStructInstance -> dt.subType!!.memsize(this)
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
+6 -3
View File
@@ -6,12 +6,15 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
+14 -38
View File
@@ -3,16 +3,20 @@ package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@@ -70,16 +74,11 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val filename = programNameWithPath.name
if(programNameWithPath.isReadable()) {
vm.runProgram(programNameWithPath.readText(), quiet)
} else {
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
override fun isIOAddress(address: UInt): Boolean = false
@@ -88,31 +87,6 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
}
@@ -130,4 +104,6 @@ private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_PTR: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
}
@@ -197,6 +197,7 @@ object AtasciiEncoding {
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\r' -> 0x9bu
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
@@ -285,6 +285,7 @@ object C64osEncoding {
val screencode = encodingC64os[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 13u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@@ -5,19 +5,19 @@ import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
object Encoder: IStringEncoding {
class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
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.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.C64OS -> C64osEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@@ -30,12 +30,12 @@ object Encoder: IStringEncoding {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
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.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.C64OS -> C64osEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
@@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
@@ -112,9 +113,14 @@ object KatakanaEncoding {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
@@ -21,7 +21,7 @@ object PetsciiEncoding {
'\ufffe', // 0x07 -> UNDEFINED
'\uf118', // 0x08 -> DISABLE CHARACTER SET SWITCHING (CUS)
'\uf119', // 0x09 -> ENABLE CHARACTER SET SWITCHING (CUS)
'\ufffe', // 0x0A -> UNDEFINED
'\n', // 0x0A -> LINE FEED (RETURN)
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
@@ -1117,6 +1117,8 @@ object PetsciiEncoding {
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 141u
'\r' -> 141u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@@ -14,14 +14,9 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x75u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
override val SCRATCH_PTR = 0x0bu // temp storage for a pointer $0b+$0c
init {
if (options.floats) {
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
@@ -9,6 +9,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
override val SCRATCH_PTR = 0x9bu // temp storage for a pointer $9b+$9c
init {
@@ -21,7 +22,6 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x02u..0xffu)
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u))
free.removeAll(arrayOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@@ -9,6 +9,7 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x7bu // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
override val SCRATCH_PTR = 0x22u // temp storage for a pointer $22+$23
init {
@@ -10,6 +10,8 @@ class ConfigurableZeropage(
override val SCRATCH_REG: UInt, // temp storage for a register byte, must be B1+1
override val SCRATCH_W1: UInt, // temp storage 1 for a word
override val SCRATCH_W2: UInt, // temp storage 2 for a word
override val SCRATCH_PTR: UInt, // temp storage for a pointer
val virtualRegistersStart: UInt, // location of 32 bytes for the r0-r15 virtual registers
basicsafe: List<UIntRange>,
kernalsafe: List<UIntRange>,
@@ -19,7 +21,7 @@ class ConfigurableZeropage(
init {
if (options.floats) {
TODO("floats")
TODO("floats in configurable target zp")
}
if(SCRATCH_REG!=SCRATCH_B1+1u)
@@ -30,7 +32,7 @@ class ConfigurableZeropage(
ZeropageType.FULL -> fullsafe.forEach { free.addAll(it) }
ZeropageType.BASICSAFE -> basicsafe.forEach { free.addAll(it) }
ZeropageType.KERNALSAFE -> kernalsafe.forEach { free.addAll(it) }
ZeropageType.FLOATSAFE -> TODO("floatsafe")
ZeropageType.FLOATSAFE -> TODO("floatsafe in configurable target zp")
}
val distinctFree = free.distinct()
@@ -14,12 +14,9 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0xb4u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
override val SCRATCH_PTR = 0xb1u // temp storage for a pointer $b1+$b2
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
@@ -38,7 +35,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb3u..0xbau) // TODO more?
free.addAll(0xb1u..0xbau) // TODO more?
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
+1 -5
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -9,12 +7,10 @@ dependencies {
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.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.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
sourceSets {
-1
View File
@@ -13,7 +13,6 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<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>
File diff suppressed because it is too large. Load diff
@@ -73,6 +73,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationT
numberOfOptimizations++
}
mods = optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
return numberOfOptimizations
}
@@ -408,7 +415,7 @@ private fun optimizeStoreLoadSame(
// a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used)
val loadinstruction = second.substring(0, 3)
val loadinstruction = second.take(3)
lines[0].value.trimStart().startsWith(loadinstruction)
}
else {
@@ -446,15 +453,30 @@ private fun optimizeStoreLoadSame(
// lda X + sta X, ldy X + sty X, ldx X + stx X -> the second instruction can be eliminated
if ((first.startsWith("lda ") && second.startsWith("sta ")) ||
(first.startsWith("ldx ") && second.startsWith("stx ")) ||
(first.startsWith("ldy ") && second.startsWith("sty "))
if (first.startsWith("lda ") && second.startsWith("sta ") ||
first.startsWith("ldx ") && second.startsWith("stx ") ||
first.startsWith("ldy ") && second.startsWith("sty ")
) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc)
mods.add(Modification(lines[2].index, true, null))
}
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated IF NOT IO ADDRESS
if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
) {
val firstVal = first.substring(4).trimStart()
val thirdVal = third.substring(4).trimStart()
if (firstVal == thirdVal) {
val address = getAddressArg(third, symbolTable)
if (address != null && !machine.isIOAddress(address)) {
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
return mods
}
@@ -512,6 +534,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp (bra bra / jmp jmp are not removed because this is likely a jump table!)
// and some other optimizations.
val mods = mutableListOf<Modification>()
@@ -567,6 +590,15 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
}
}
// only remove bra followed by jmp or jmp followed by bra
// bra bra or jmp jmp is likely part of a jump table, which should keep all entries!
if((" bra" in first || "\tbra" in first) && (" jmp" in second || "\tjmp" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
if((" jmp" in first || "\tjmp" in first) && (" bra" in second || "\tbra" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
}
/*
@@ -684,23 +716,32 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
optimize('x', lines)
optimize('y', lines)
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
val third = lines[3].value.trimStart()
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
// phy + ldy + pla -> tya + ldy
// phx + ldx + pla -> txa + ldx
// pha + lda + pla -> nop
// pha + tya + tay + pla -> nop
// pha + txa + tax + pla -> nop
when (first) {
"phy" if second.startsWith("ldy ") && third=="pla" -> {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " tya"))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[0].index, false, " tya"))
}
"phx" if second.startsWith("ldx ") && third=="pla" -> {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " txa"))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[0].index, false, " txa"))
}
"pha" if second.startsWith("lda ") && third=="pla" -> {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
"pha" if ((second=="tya" && third=="tay") || (second=="txa" && third=="tax")) && fourth=="pla" -> {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
@@ -712,7 +753,6 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
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>()
@@ -757,3 +797,120 @@ private fun optimizeUnneededTempvarInAdd(linesByFour: Sequence<List<IndexedValue
return mods
}
private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen: Sequence<List<IndexedValue<String>>>): List<Modification> {
/*
; FIRST SEQUYENCE: P8ZP_SCRATCH_PTR += AY :
clc
adc P8ZP_SCRATCH_PTR
pha
tya
adc P8ZP_SCRATCH_PTR+1
tay
pla
sta P8ZP_SCRATCH_PTR
sty P8ZP_SCRATCH_PTR+1
->
clc
adc P8ZP_SCRATCH_PTR
sta P8ZP_SCRATCH_PTR
tya
adc P8ZP_SCRATCH_PTR+1
sta P8ZP_SCRATCH_PTR+1
also SECOND SEQUENCE:
ldx VALUE/ ldy VALUE
sta SOMEWHERE_WITHOUT_,x_OR_,y
txa / tya
ldy #1
sta SOMEWHERE
-->
sta SOMEWHERE_WITHOUT_,x_OR_,y
lda VALUE
ldy #1
sta SOMEWHERE
also THIRD SEQUENCE:
ldx VALUE
ldy #0
sta SOMEWHERE_WITHOUT_,x
txa
iny
sta SOMEWHERE
-->
ldy #0
sta SOMEWHERE_WITHOUT_,x
lda VALUE
iny
sta SOMEWHERE
*/
val mods = mutableListOf<Modification>()
for (lines in linesByFourteen) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
val fifth = lines[4].value.trimStart()
val sixth = lines[5].value.trimStart()
val seventh = lines[6].value.trimStart()
val eight = lines[7].value.trimStart()
val ninth = lines[8].value.trimStart()
// FIRST SEQUENCE
if(first=="clc" && second.startsWith("adc") && third=="pha" && fourth=="tya" &&
fifth.startsWith("adc") && sixth=="tay" && seventh=="pla" && eight.startsWith("sta") && ninth.startsWith("sty")) {
val var2 = second.substring(4)
val var5 = fifth.substring(4).substringBefore('+')
val var8 = eight.substring(4)
val var9 = ninth.substring(4).substringBefore('+')
if(var2==var5 && var2==var8 && var2==var9) {
if(fifth.endsWith("$var5+1") && ninth.endsWith("$var9+1")) {
mods.add(Modification(lines[2].index, false, " sta $var2"))
mods.add(Modification(lines[5].index, false, " sta $var2+1"))
mods.add(Modification(lines[6].index, true, null))
mods.add(Modification(lines[7].index, true, null))
mods.add(Modification(lines[8].index, true, null))
}
}
}
// SECOND SEQUENCE
if(first.startsWith("ldx ") && second.startsWith("sta ") &&
third=="txa" && fourth.startsWith("ldy ") && fifth.startsWith("sta ")
) {
if(",x" !in second) {
val value = first.substring(4)
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[2].index, false, " lda $value"))
}
}
if(first.startsWith("ldy ") && second.startsWith("sta ") &&
third=="tya" && fourth.startsWith("ldy ") && fifth.startsWith("sta ")
) {
if(",y" !in second) {
val value = first.substring(4)
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[2].index, false, " lda $value"))
}
}
// THIRD SEQUENCE
if(first.startsWith("ldx ") && second.startsWith("ldy ") && third.startsWith("sta ") &&
fourth=="txa" && fifth=="iny" && sixth.startsWith("sta ")
) {
if(",x" !in third) {
val value = first.substring(4)
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[3].index, false, " lda $value"))
}
}
}
return mods
}
@@ -28,6 +28,14 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
command.addAll(options.compTarget.additionalAssemblerOptions)
command.addAll(listOf("--output", program.toString(), assembly.toString()))
return command
}
when(options.output) {
OutputType.PRG -> {
// CBM machines .prg generation.
@@ -47,8 +55,7 @@ internal class AssemblyProgram(
command.add("--list=$listFile")
}
command.addAll(listOf("--output", prgFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
if(!options.quiet)
println("\nCreating prg for target ${compTarget.name}.")
}
@@ -69,8 +76,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", xexFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
if(!options.quiet)
println("\nCreating xex for target ${compTarget.name}.")
}
@@ -90,8 +96,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
if(!options.quiet)
println("\nCreating raw binary for target ${compTarget.name}.")
}
@@ -122,14 +127,10 @@ internal class AssemblyProgram(
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
}
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
}
if(options.compTarget.additionalAssemblerOptions!=null)
assemblerCommand.add(options.compTarget.additionalAssemblerOptions!!)
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()
@@ -1,5 +1,8 @@
package prog8.codegen.cpu6502
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -24,15 +27,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val sscope = fcall.definingISub()
when (fcall.name) {
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
"msw" -> funcMsw(fcall, resultRegister)
"lsw" -> funcLsw(fcall, resultRegister)
"msb" -> funcMsb(fcall, resultRegister)
"lsb" -> funcLsb(fcall, resultRegister)
"mkword" -> funcMkword(fcall, resultRegister)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(fcall, resultRegister)
"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)
"mklong", "mklong2" -> funcMklong(fcall) // result is in R0:R1
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(fcall, resultRegister)
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(fcall, resultRegister)
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(fcall, resultRegister)
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
"sgn" -> funcSgn(fcall, resultRegister, sscope)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
"divmod__ubyte" -> funcDivmod(fcall)
@@ -45,9 +49,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"setmsb" -> funcSetLsbMsb(fcall, true)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekl" -> funcPeekL(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peekbool" -> funcPeekBool(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokel" -> funcPokeL(fcall)
"pokef" -> funcPokeF(fcall)
"pokemon" -> {
val memread = PtMemoryByte(fcall.position)
@@ -60,12 +67,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" pla")
}
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"pokebool" -> funcPokeBool(fcall)
"rsave" -> funcRsave()
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"callfar2" -> funcCallFar2(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_lib_structalloc" -> funcStructAlloc(fcall, discardResult, resultRegister)
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister)
@@ -340,8 +349,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else {
// arg1 is a word
} else if(arg1.type.isWord) {
if(arg2.type.isWord) {
when (arg2) {
is PtIdentifier -> {
@@ -372,6 +380,60 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else if(arg1.type.isLong) {
if(arg2.type.isLong) {
if(arg1 is PtIdentifier && arg2 is PtNumber) {
val var1 = asmgen.asmVariableName(arg1)
val hex = arg2.number.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $var1
cmp #${hex.substring(6, 8)}
bne +
lda $var1+1
cmp #${hex.substring(4, 6)}
bne +
lda $var1+2
cmp #${hex.substring(2, 4)}
bne +
lda $var1+3
cmp #${hex.take(2)}
+""")
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
val var1 = asmgen.asmVariableName(arg1)
val var2 = asmgen.asmVariableName(arg2)
asmgen.out("""
lda $var1
cmp $var2
bne +
lda $var1+1
cmp $var2+1
bne +
lda $var1+2
cmp $var2+2
bne +
lda $var1+3
cmp $var2+3
+""")
} else {
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R2R3_32, true)
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R0R1_32, true)
asmgen.out("""
lda cx16.r0
cmp cx16.r2
bne +
lda cx16.r0+1
cmp cx16.r2+1
bne +
lda cx16.r0+2
cmp cx16.r2+2
bne +
lda cx16.r0+3
cmp cx16.r2+3
+""")
}
// TODO: carry flag is not correct......
} else
throw AssemblyError("args for cmp() should have same dt")
}
}
@@ -380,8 +442,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(fcall.position)
val slabname = PtIdentifier("$StMemorySlabBlockName.memory_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
@@ -390,6 +453,22 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcStructAlloc(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult)
throw AssemblyError("should not discard result of struct allocation at $fcall")
// ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it
val prefix = if(fcall.args.isEmpty()) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
val labelname = PtIdentifier("$prefix.${SymbolTable.labelnameForStructInstance(fcall)}", fcall.type, fcall.position)
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(labelname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf)
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
val assign = AsmAssignment(src, listOf(target), program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcSqrt(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
when(fcall.args[0].type.base) {
@@ -401,6 +480,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
BaseDataType.LONG -> TODO("sqrt LONG")
BaseDataType.FLOAT -> {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
@@ -415,8 +495,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
@@ -438,8 +521,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
else
@@ -452,6 +537,26 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("ror2 long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out("""
lsr $variable+3
ror $variable+2
ror $variable+1
ror $variable
bcc +
lda $variable+3
ora #$80
sta $variable+3
+""")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -465,7 +570,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" ror ${varname},x")
}
is PtMemoryByte -> {
@@ -498,7 +606,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
else
@@ -511,6 +621,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("ror long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+3 | ror $variable+2 | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -521,8 +642,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
@@ -545,7 +668,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (what) {
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
else
@@ -558,6 +683,24 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("rol2 long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out("""
asl $variable
rol $variable+1
rol $variable+2
rol $variable+3
bcc +
inc $variable
+""")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -571,7 +714,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" rol ${varname},x")
}
is PtMemoryByte -> {
@@ -604,7 +749,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
else
@@ -617,6 +764,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("rol long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1 | rol $variable+2 | rol $variable+3")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -633,7 +791,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
val arrayVariable = indexer.variable ?: TODO("support for ptr indexing ${indexer.position}")
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.arrayFor(BaseDataType.UBYTE, false), arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
@@ -688,6 +846,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
BaseDataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
BaseDataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
BaseDataType.LONG -> TODO("sgn LONG")
BaseDataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
@@ -706,6 +865,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, scope, asmgen), RegisterOrPair.AY)
}
BaseDataType.LONG -> {
asmgen.out(" jsr prog8_lib.abs_l_into_R0R1")
assignAsmGen.assignRegisterLong(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.R0R1_32, true, fcall.position, scope, asmgen), RegisterOrPair.R0R1_32)
}
BaseDataType.FLOAT -> {
asmgen.out(" jsr floats.func_abs_f_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
@@ -739,7 +902,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
@@ -754,6 +917,52 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPokeBool(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr")
return
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
asmgen.storeIndirectByteReg(CpuRegister.A, varname, 0u, false, false)
return
}
}
is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
// can do ZP,Y indexing
val varname = asmgen.asmVariableName(pointer)
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
asmgen.out(" sta ($varname),y")
return
}
}
else -> { /* fall through */ }
}
// fall through method:
if(fcall.args[1].isSimple()) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
} else {
asmgen.pushCpuStack(BaseDataType.UBYTE, fcall.args[1])
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.restoreRegisterStack(CpuRegister.A, false)
}
asmgen.storeIndirectByteReg(CpuRegister.A, "P8ZP_SCRATCH_W1", 0u, false, false)
}
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
@@ -767,20 +976,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
sta ($varname)
txa
ldy #1
sta ($varname),y""")
} else {
asmgen.out("""
ldy #0
sta ($varname),y
txa
iny
sta ($varname),y""")
}
asmgen.storeIndirectWordReg(RegisterOrPair.AX, varname, 0u)
return
}
}
@@ -810,12 +1006,57 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPokeL(fcall: PtBuiltinFunctionCall) {
val addrExpr = fcall.args[0]
val value = fcall.args[1]
TODO("pokel $addrExpr, $value")
}
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr floats.MOVFM")
if(resultRegister!=null) {
assignAsmGen.assignFAC1float(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position))
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), register=resultRegister, position=fcall.position))
}
}
private fun funcPeekBool(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
fun fallback() {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peek")
}
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr")
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr))
asmgen.loadIndirectByte(varname, 0u)
else
fallback()
}
is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
// can do ZP,Y indexing
val varname = asmgen.asmVariableName(pointer)
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.out(" lda ($varname),y")
} else fallback()
}
else -> fallback()
}
when(resultRegister ?: RegisterOrPair.A) {
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" tax")
RegisterOrPair.Y -> asmgen.out(" tay")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().lowercase()}")
else -> throw AssemblyError("invalid reg")
}
}
@@ -831,25 +1072,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
if (asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
ldy #1
lda ($varname),y
tay
lda ($varname)""")
} else {
asmgen.out("""
ldy #0
lda ($varname),y
tax
iny
lda ($varname),y
tay
txa""")
}
} else fallback()
if(asmgen.isZpVar(addrExpr))
asmgen.loadIndirectWord(varname, 0u)
else
fallback()
}
is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
@@ -882,6 +1108,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPeekL(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val addrExpr = fcall.args[0]
val value = fcall.args[1]
TODO("peekl $addrExpr, $value")
}
private fun funcClamp(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type.isSigned
when {
@@ -901,6 +1134,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
assignAsmGen.assignRegisterpairWord(targetReg, RegisterOrPair.AY)
}
fcall.type.isLong -> {
TODO("clamp long ${fcall.position}")
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -957,6 +1193,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
fcall.type.isLong -> {
TODO("min long ${fcall.position}")
}
else -> {
throw AssemblyError("min float not supported")
}
@@ -965,55 +1204,109 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMax(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type.isSigned
if(fcall.type.isByte) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
asmgen.out("""
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
} else if(fcall.type.isWord) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
when {
fcall.type.isByte -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if (signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bmi +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
+"""
)
val targetReg = AsmAssignTarget.fromRegisters(
resultRegister ?: RegisterOrPair.A,
signed,
fcall.position,
fcall.definingISub(),
asmgen
)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
}
fcall.type.isWord -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if (signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bmi +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
}
val targetReg = AsmAssignTarget.fromRegisters(
resultRegister ?: RegisterOrPair.AY,
signed,
fcall.position,
fcall.definingISub(),
asmgen
)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
fcall.type.isLong -> {
TODO("max long ${fcall.position}")
}
else -> {
throw AssemblyError("max float not supported")
}
}
}
private fun funcMklong(fcall: PtBuiltinFunctionCall) {
// result long in R0:R1 (r0=lsw, r1=msw)
fun isArgRegister(expression: PtExpression, reg: RegisterOrPair): Boolean {
if(expression !is PtIdentifier)
return false
return expression.name.startsWith("cx16.${reg.name.lowercase()}")
}
if(fcall.args.size==2) {
// mklong2(msw, lsw)
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1)) {
error("cannot use R0 and/or R1 as arguments for mklong2 because the result should go into R0:R1 ${fcall.position}")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1", DataType.UWORD)
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r0", DataType.UWORD)
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
} else {
throw AssemblyError("max float not supported")
// mklong(msb, b2, b1, lsb)
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1) ||
isArgRegister(fcall.args[2], RegisterOrPair.R0) || isArgRegister(fcall.args[2], RegisterOrPair.R1) ||
isArgRegister(fcall.args[3], RegisterOrPair.R0) || isArgRegister(fcall.args[3], RegisterOrPair.R1)) {
error("cannot use R0 and/or R1 as arguments for mklong because the result should go into R0:R1 ${fcall.position}")
} else {
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1H", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r1L", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[2], "cx16.r0H", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[3], "cx16.r0L", DataType.UBYTE)
}
}
}
@@ -1087,7 +1380,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isWord)
throw AssemblyError("msb required word argument")
throw AssemblyError("msb requires word argument")
if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is PtIdentifier) {
@@ -1112,7 +1405,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(arg is PtArrayIndexer && resultRegister in arrayOf(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"
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = asmgen.asmVariableName(arg.variable!!)+"_msb"
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@@ -1129,7 +1424,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
val arrayVar = asmgen.asmVariableName(arg.variable)
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = asmgen.asmVariableName(arg.variable!!)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@@ -1211,7 +1508,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
if(arg is PtArrayIndexer && resultRegister in arrayOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the lsb byte out of the word array
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable!!)+"_lsb" else asmgen.asmVariableName(arg.variable!!)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@@ -1266,6 +1566,53 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcMsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isLong)
throw AssemblyError("msw requires long argument")
if (arg is PtNumber)
throw AssemblyError("msw(const) should have been const-folded away")
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultRegister) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+2 | ldx $sourceName+3")
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName+2 | ldy $sourceName+3")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+2 | ldy $sourceName+3")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
asmgen.out(" lda $sourceName+2 | sta cx16.$regname | lda $sourceName+3 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
} else {
TODO("msw(expression) ${fcall.position} - use a temporary variable for now")
}
}
private fun funcLsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isLong)
throw AssemblyError("lsw requires long argument")
if (arg is PtNumber)
throw AssemblyError("lsw(const) should have been const-folded away")
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultRegister) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
asmgen.out(" lda $sourceName | sta cx16.$regname | lda $sourceName+1 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
} else {
TODO("lsw(expression) ${fcall.position} - use a temporary variable for now")
}
}
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map {
@@ -1276,7 +1623,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
fun getSourceForFloat(value: PtExpression): AsmAssignSource {
return when (value) {
is PtIdentifier -> {
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), false, value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -1290,7 +1637,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), false, value.position)
addr.add(variable)
addr.parent = call
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT)
@@ -1308,9 +1655,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
val src = when {
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt==BaseDataType.LONG -> TODO("LONG argument for builtin func")
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -1326,9 +1674,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.reg != null -> {
val src = when {
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt==BaseDataType.LONG -> AsmAssignSource.fromAstSource(value, program, asmgen)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -1337,7 +1686,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
AsmAssignSource.fromAstSource(value, program, asmgen)
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, value.position, null, asmgen)
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, conv.dt.isSigned, value.position, null, asmgen)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, value.position)
asmgen.translateNormalAssignment(assign, scope)
}
@@ -89,7 +89,7 @@ internal class ForLoopsAsmGen(
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
asmgen.out(" sta $toValueVar")
// pre-check for end already reached
@@ -296,7 +296,7 @@ $modifiedLabel cmp #0 ; modified
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out(" sta $toValueVar")
@@ -420,6 +420,7 @@ $endLabel""")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable; there is self-modifying code below
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@@ -442,7 +443,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -500,143 +500,185 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
val numElements: UInt = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
else -> 0u
}
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.getTempVarName(BaseDataType.UBYTE)
asmgen.out("""
fun iterateStrings() {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$endLabel""")
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$indexVar .byte 0
$endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
asmgen.out("""
}
fun iterateBytes() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateSplitWords() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda ${iterableName}_lsb,y
sta $loopvarName
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val length = numElements * 2
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateWords(actuallyLongs: Boolean = false) {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127) {
if(actuallyLongs) {
asmgen.out("""
lda $iterableName+2,y
sta $loopvarName+2
lda $iterableName+3,y
sta $loopvarName+3""")
}
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
iny
iny
cpy #${numElements*4u}
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
}
} else {
if(actuallyLongs) {
// array size is 64 longs, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
bne $loopLabel
beq $endLabel""")
} else {
// array size is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
@@ -644,23 +686,29 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(length>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
iterableDt.isFloatArray -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
asmgen.out(endLabel)
}
when {
iterableDt.isString -> iterateStrings()
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
iterableDt.isSplitWordArray -> iterateSplitWords()
iterableDt.isWordArray -> iterateWords()
iterableDt.isLongArray -> iterateWords(true)
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.removeLast()
@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we consider them NOT to be optimized into (possibly different) CPU registers.
// Just load them in whatever the register spec says.
return when (params.size) {
1 -> params[0].type.isIntegerOrBool && params[0].register == null
1 -> params[0].register == null && (params[0].type.isWordOrByteOrBool || params[0].type.isPointer)
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
else -> false
}
@@ -36,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
return
}
@@ -139,15 +139,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
else if(sub is PtSub) {
if(optimizeIntArgsViaCpuRegisters(sub.parameters)) {
val parameters = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(optimizeIntArgsViaCpuRegisters(parameters)) {
// Note that if the args fit into cpu registers, we don't concern ourselves here
// if they should be put into regular subroutine parameter variables, or the R0-R15 register variables.
// That is now up to the subroutine itself.
useCpuRegistersForArgs(call.args, sub)
} else {
// arguments via variables
val paramValues = sub.parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { it.first.register == null }
val paramValues = parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) }
if (normalParams.isNotEmpty()) {
for (p in normalParams)
argumentViaVariable(sub, p.first, p.second)
@@ -166,7 +167,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun useCpuRegistersForArgs(args: List<PtExpression>, sub: PtSub) {
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
when(params.size) {
1 -> {
val register = if (params[0].type.isByteOrBool) RegisterOrPair.A else RegisterOrPair.AY
@@ -205,8 +206,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
if (arg.name == "mkword" || arg.name == "mklong2")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
if (arg.name == "mklong")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
return !arg.isSimple()
}
is PtAddressOf -> false
@@ -332,14 +336,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
val scope = value.definingISub()
val target: AsmAssignTarget =
if(parameter.value.type.isByte && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
if(parameter.value.type.isByte && register.isWord())
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
else {
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
@@ -6,11 +6,13 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.AssignmentAsmGen
import prog8.codegen.cpu6502.assignment.PointerAssignmentsGen
import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class IfElseAsmGen(private val program: PtProgram,
private val st: SymbolTable,
private val asmgen: AsmGen6502Internal,
private val pointergen: PointerAssignmentsGen,
private val assignmentAsmGen: AssignmentAsmGen,
private val errors: IErrorReporter) {
@@ -43,7 +45,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
val rightDt = compareCond.right.type
return when {
rightDt.isByteOrBool -> translateIfByte(stmt, jumpAfterIf)
rightDt.isWord -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isWord || rightDt.isPointer -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isLong -> translateIfLong(stmt, compareCond, jumpAfterIf)
rightDt.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
else -> throw AssemblyError("weird dt")
}
@@ -63,6 +66,16 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
val dereference = stmt.condition as? PtPointerDeref
if(dereference!=null) {
val (zpPtrVar, offset) = pointergen.deref(dereference)
asmgen.loadIndirectByte(zpPtrVar, offset)
return if (jumpAfterIf != null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
}
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
}
@@ -149,6 +162,93 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodiesSignedByte(elseConditional: String, value: PtExpression, stmt: PtIfElse) {
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
val afterIfLabel = asmgen.makeLabel("afterif")
asmgen.cmpAwithByteValue(value, true)
if(stmt.hasElse()) {
// if and else blocks
val elseLabel = asmgen.makeLabel("else")
branchElse(elseLabel)
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
// no else block
branchElse(afterIfLabel)
asmgen.translate(stmt.ifScope)
}
asmgen.out(afterIfLabel)
}
private fun translateJumpElseBodiesSignedByte(elseConditional: String, value: PtExpression, jump: PtJump, elseBlock: PtNodeGroup) {
fun branchTarget(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
var target = asmgen.getJumpTarget(jump, false)
asmgen.cmpAwithByteValue(value, true)
if(target.indirect) {
branchElse("+")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
branchTarget(target.asmLabel)
}
asmgen.translate(elseBlock)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@@ -212,38 +312,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", stmt)
}
"<" -> translateByteLess(stmt, signed, jumpAfterIf)
"<=" -> {
// X<=Y -> Y>=X (reverse of >=)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.left, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
"<=" -> translateByteLessEqual(stmt, signed, jumpAfterIf)
">" -> translateByteGreater(stmt, signed, jumpAfterIf)
">=" -> {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
">=" -> translateByteGreaterEqual(stmt, signed, jumpAfterIf)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
@@ -406,13 +477,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun translateByteLess(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcc", "bcs", jumpAfterIf, stmt.elseScope)
else
@@ -420,16 +491,33 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteLessEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
// X<=Y -> Y>=X (reverse of >=)
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.left, stmt)
} else {
asmgen.cmpAwithByteValue(condition.left, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateByteGreater(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
asmgen.cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.left, stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A)
asmgen.cmpAwithByteValue(condition.right, false)
@@ -471,6 +559,49 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteGreaterEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateIfLong(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val constValue = condition.right.asConstInteger()
if(constValue==0) {
// optimized comparisons with zero
return when (condition.operator) {
"==" -> longEqualsZero(condition.left, false, jumpAfterIf, stmt)
"!=" -> longEqualsZero(condition.left, true, jumpAfterIf, stmt)
"<" -> longLessZero(condition.left, false, jumpAfterIf, stmt)
"<=" -> longLessZero(condition.left, true, jumpAfterIf, stmt)
">" -> longGreaterZero(condition.left, false, jumpAfterIf, stmt)
">=" -> longGreaterZero(condition.left, true, jumpAfterIf, stmt)
else -> throw AssemblyError("expected comparison operator")
}
}
return when (condition.operator) {
"==" -> longEqualsValue(condition.left, condition.right, false, jumpAfterIf, stmt)
"!=" -> longEqualsValue(condition.left, condition.right, true, jumpAfterIf, stmt)
"<" -> TODO("long < 0")
"<=" -> TODO("long <= 0")
">" -> TODO("long > 0")
">=" -> TODO("long >= 0")
else -> throw AssemblyError("expected comparison operator")
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -502,7 +633,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordGreaterEqualsZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word>=0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
@@ -515,7 +646,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordLessZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word<0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
@@ -790,33 +921,44 @@ _jump jmp (${target.asmLabel})
}
private fun loadAndCmp0MSB(value: PtExpression) {
private fun loadAndCmp0MSB(value: PtExpression, long: Boolean) {
when(value) {
is PtArrayIndexer -> {
val varname = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varname = asmgen.asmVariableName(value.variable!!)
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords)
if(value.splitWords) {
require(!long)
asmgen.out(" lda ${varname}_msb,y")
}
else if(long)
asmgen.out(" lda $varname+3,y")
else
asmgen.out(" lda $varname+1,y")
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(value)
asmgen.out(" lda $varname+1")
if(long)
asmgen.out(" lda $varname+3")
else
asmgen.out(" lda $varname+1")
}
is PtAddressOf -> {
require(!long)
if(value.isFromArrayElement) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(value.identifier)
if(value.identifier.type.isSplitWordArray) {
var varname = asmgen.asmVariableName(value.identifier!!)
if(value.identifier!!.type.isSplitWordArray) {
varname += if(value.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
}
}
else -> {
require(!long)
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
}
@@ -888,8 +1030,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@@ -1035,8 +1179,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@@ -1116,6 +1262,50 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsZero(value: PtExpression, notEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3""")
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long <= 0")
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
}
}
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long >= 0")
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
}
}
private fun wordEqualsZero(value: PtExpression, notEquals: Boolean, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for (u)word == 0
@@ -1149,7 +1339,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if(constIndex!=null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "bne", "beq")
}
@@ -1170,7 +1362,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if (constIndex != null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "beq", "bne")
}
@@ -1189,6 +1383,93 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsValue(
left: PtExpression,
right: PtExpression,
notEquals: Boolean,
jump: PtJump?,
stmt: PtIfElse
) {
// TODO this can be optimized somewhat more when the left operand is a variable as well
// we only optimize for a const right value for now
val constRight = right.asConstInteger()
val variableRight = (right as? PtIdentifier)?.name
if(constRight!=null) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
val hex = constRight.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda cx16.r0
cmp #$${hex.substring(6,8)}
bne +
lda cx16.r0+1
cmp #$${hex.substring(4, 6)}
bne +
lda cx16.r0+2
cmp #$${hex.substring(2, 4)}
bne +
lda cx16.r0+3
cmp #$${hex.take(2)}
+""")
} else if(variableRight!=null) {
require(right.type.isLong)
val variableLeft = (left as? PtIdentifier)?.name
if(variableLeft!=null) {
asmgen.out("""
lda #<$variableLeft
ldy #>$variableLeft
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$variableRight
ldy #>$variableRight
jsr prog8_lib.long_not_equals""")
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
asmgen.out("""
lda cx16.r0
cmp $variableRight
bne +
lda cx16.r0+1
cmp $variableRight+1
bne +
lda cx16.r0+2
cmp $variableRight+2
bne +
lda cx16.r0+3
cmp $variableRight+3
+""")
}
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R2R3_32, left.type.isSigned)
asmgen.assignExpressionToRegister(right, RegisterOrPair.R0R1_32, right.type.isSigned)
asmgen.out("""
lda cx16.r0
cmp cx16.r2
bne +
lda cx16.r0+1
cmp cx16.r2+1
bne +
lda cx16.r0+2
cmp cx16.r2+2
bne +
lda cx16.r0+3
cmp cx16.r2+3
+""")
}
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun wordEqualsValue(
left: PtExpression,
right: PtExpression,
@@ -1551,8 +1832,10 @@ _jump jmp (${target.asmLabel})
fun translateEqualsArray(left: PtArrayIndexer, right: PtExpression) {
val constIndex = left.index.asConstInteger()
if(constIndex!=null) {
if(left.variable==null)
TODO("support for ptr indexing ${left.position}")
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varName = asmgen.asmVariableName(left.variable)
val varName = asmgen.asmVariableName(left.variable!!)
if(left.splitWords) {
return if(notEquals)
translateAYNotEquals("${varName}_lsb+$constIndex", "${varName}_msb+$constIndex")
@@ -1615,13 +1898,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYNotEquals("#<$varname", "#>$varname")
@@ -1667,13 +1950,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYEquals("#<$varname", "#>$varname")
@@ -9,7 +9,9 @@ import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, private val assignmentAsmGen: AssignmentAsmGen, private val errors: IErrorReporter) {
internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) {
require(target.datatype==expr.type || target.datatype.isUnsignedWord && expr.type.isString)
require(target.datatype==expr.type ||
target.datatype.isUnsignedWord && (expr.type.isString || expr.type.isPointer) ||
target.datatype.isPointer && (expr.type.isUnsignedWord || expr.type.isPointer || expr.type.isString))
val falseLabel = asmgen.makeLabel("ifexpr_false")
val endLabel = asmgen.makeLabel("ifexpr_end")
evalIfExpressionConditonAndBranchWhenFalse(expr.condition, falseLabel)
@@ -30,6 +32,14 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
expr.type.isLong -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.R0R1_32, true)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.R0R1_32, true)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R0R1_32)
}
expr.type.isFloat -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
@@ -42,6 +52,69 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
internal fun assignBranchCondExpression(target: AsmAssignTarget, expr: PtBranchCondExpression) {
require(target.datatype==expr.type ||
target.datatype.isUnsignedWord && (expr.type.isString || expr.type.isPointer) ||
target.datatype.isPointer && (expr.type.isUnsignedWord || expr.type.isPointer || expr.type.isString))
if(target.kind==TargetStorageKind.REGISTER && target.datatype.isUnsignedByte && target.register==RegisterOrPair.A) {
if(expr.condition==BranchCondition.CC) {
if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) {
asmgen.out(" lda #0 | rol a")
return
}
else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) {
asmgen.out(" lda #0 | rol a | eor #1")
return
}
}
else if(expr.condition==BranchCondition.CS) {
if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) {
asmgen.out(" lda #0 | rol a | eor #1")
return
}
else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) {
asmgen.out(" lda #0 | rol a")
return
}
}
}
val trueLabel = asmgen.makeLabel("branchexpr_true")
val endLabel = asmgen.makeLabel("branchexpr_end")
val branch = asmgen.branchInstruction(expr.condition, false)
asmgen.out(" $branch $trueLabel")
when {
expr.type.isByteOrBool -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
expr.type.isFloat -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.out(endLabel)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt")
}
}
private fun evalIfExpressionConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) {
when (condition) {
is PtBinaryExpression -> {
@@ -49,6 +122,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
return when {
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
rightDt.isLong -> translateIfExpressionLongConditionBranch(condition, falseLabel)
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
@@ -135,6 +209,36 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionLongConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
// TODO can we reuse this whole thing from IfElse ?
val constValue = condition.right.asConstInteger()
if(constValue!=null) {
if (constValue == 0) {
when (condition.operator) {
"==" -> return translateLongExprIsZero(condition.left, falseLabel)
"!=" -> return translateLongExprIsNotZero(condition.left, falseLabel)
}
}
if (constValue != 0) {
when (condition.operator) {
"==" -> return translateLongExprEqualsNumber(condition.left, constValue, falseLabel)
"!=" -> return translateLongExprNotEqualsNumber(condition.left, constValue, falseLabel)
}
}
}
val variable = condition.right as? PtIdentifier
if(variable!=null) {
when (condition.operator) {
"==" -> return translateLongExprEqualsVariable(condition.left, variable, falseLabel)
"!=" -> return translateLongExprNotEqualsVariable(condition.left, variable, falseLabel)
}
}
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
asmgen.assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
val constValue = (condition.right as? PtNumber)?.number
if(constValue==0.0) {
@@ -274,6 +378,89 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
// if L==number
// TODO reuse code from ifElse?
val hex = number.toUInt().toString(16).padStart(8, '0')
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp #${hex.substring(6, 8)}
bne $falseLabel
lda $varname+1
cmp #${hex.substring(4, 6)}
bne $falseLabel
lda $varname+2
cmp #${hex.substring(2, 4)}
bne $falseLabel
lda $varname+3
cmp #${hex.take(2)}
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
cmp #${hex.substring(6, 8)}
bne $falseLabel
lda cx16.r0+1
cmp #${hex.substring(4, 6)}
bne $falseLabel
lda cx16.r0+2
cmp #${hex.substring(2, 4)}
bne $falseLabel
lda cx16.r0+3
cmp #${hex.take(2)}
bne $falseLabel""")
}
}
private fun translateLongExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L==variable
// TODO reuse code from ifElse?
val varname2 = asmgen.asmVariableName(variable)
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp $varname2
bne $falseLabel
lda $varname+1
cmp $varname2+1
bne $falseLabel
lda $varname+2
cmp $varname2+2
bne $falseLabel
lda $varname+3
cmp $varname2+3
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
cmp $varname2
bne $falseLabel
lda cx16.r0+1
cmp $varname2+1
bne $falseLabel
lda cx16.r0+2
cmp $varname2+2
bne $falseLabel
lda cx16.r0+3
cmp $varname2+3
bne $falseLabel""")
}
}
private fun translateLongExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
TODO("if expression LONG != number ${expr.position}")
}
private fun translateLongExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L!=variable
TODO("if expression LONG != variable ${expr.position}")
}
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if w!=0
// TODO reuse code from ifElse?
@@ -304,6 +491,50 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprIsZero(expr: PtExpression, falseLabel: String) {
// if L==0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3
bne $falseLabel""")
}
}
private fun translateLongExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if L!=0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
beq $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3
beq $falseLabel""")
}
}
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
// optimized code for byte comparisons with 0
@@ -44,8 +44,8 @@ internal class ProgramAndVarsGen(
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
structInstances2asm()
memorySlabs()
tempVars()
footer()
}
}
@@ -71,6 +71,7 @@ internal class ProgramAndVarsGen(
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
asmgen.out("P8ZP_SCRATCH_PTR = ${zp.SCRATCH_PTR} ; word")
if(compTarget.name=="c64") {
if(options.floats)
asmgen.out("PROG8_C64_BANK_CONFIG=31 ; basic+IO+kernal")
@@ -208,41 +209,20 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section slabs_BSS")
asmgen.out("prog8_slabs\t.block")
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("$StMemorySlabBlockName\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
}
asmgen.out("\t.bend\n .send slabs_BSS")
asmgen.out("\t.bend\n .send BSS_SLABS")
}
}
private fun tempVars() {
// these can be in the no clear section because they'll always get assigned a new value
asmgen.out("; expression temp vars\n .section BSS_NOCLEAR")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.BYTE -> asmgen.out("$name .char ?")
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.WORD -> asmgen.out("$name .sint ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS_NOCLEAR")
}
private fun footer() {
asmgen.out(" .dsection STRUCTINSTANCES\n")
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
@@ -298,14 +278,14 @@ internal class ProgramAndVarsGen(
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(relocateBssVars) {
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
if(relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
@@ -314,12 +294,12 @@ internal class ProgramAndVarsGen(
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
if(relocateBssSlabs) {
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
}
}
@@ -352,10 +332,9 @@ internal class ProgramAndVarsGen(
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
val constvalue = assign.value as? PtNumber
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")
@@ -373,7 +352,7 @@ internal class ProgramAndVarsGen(
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -387,11 +366,94 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
private fun asmTypeString(dt: DataType): String {
return when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun structInstances2asm() {
fun initValues(instance: StStructInstance): List<String> {
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
if(field.first.isFloat) {
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
} else {
when {
value.number!=null -> {
if(field.first.isPointer)
"$"+value.number!!.toInt().toString(16)
else if(field.first.isInteger)
value.number!!.toInt().toString()
else
value.number.toString()
}
value.addressOfSymbol!=null -> value.addressOfSymbol!!
value.boolean!=null -> if(value.boolean==true) "1" else "0"
else -> throw AssemblyError("weird struct initial value $value")
}
}
}
}
asmgen.out("; struct types")
symboltable.allStructInstances.distinctBy { it.structName }.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${it.structName} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first
val varname = "f${index}"
val type = asmTypeString(dt)
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
}
asmgen.out(" .endstruct\n")
}
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n")
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
if(field.first.isFloat) {
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
"[${floatbytes.joinToString(",")}]"
}
else "?"
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
asmgen.out("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
}
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
return // subroutine gets inlined at call site.
@@ -441,7 +503,7 @@ internal class ProgramAndVarsGen(
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -459,7 +521,7 @@ internal class ProgramAndVarsGen(
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
entrypointInitialization()
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(functioncallAsmGen.optimizeIntArgsViaCpuRegisters(params)) {
asmgen.out("; simple int arg(s) passed via cpu register(s)")
@@ -501,8 +563,9 @@ internal class ProgramAndVarsGen(
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.LONG -> asmgen.out("$name .sint ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -515,7 +578,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
@@ -570,12 +633,12 @@ internal class ProgramAndVarsGen(
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
outputStringvar(varname, 0, it.value.second, it.value.first)
outputStringvar(varname, 0u, it.value.second, it.value.first)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
arrayVariable2asm(varname, it.alloc.dt, 0, it.value, null)
arrayVariable2asm(varname, it.alloc.dt, 0u, it.value, null)
}
asmgen.out("+")
@@ -643,7 +706,7 @@ internal class ProgramAndVarsGen(
fun generate(section: String, variables: List<StStaticVariable>) {
asmgen.out(" .section $section")
val (notAligned, aligned) = variables.partition { it.align == 0 }
val (notAligned, aligned) = variables.partition { it.align == 0u }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
}
@@ -668,8 +731,8 @@ internal class ProgramAndVarsGen(
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables with init value")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt.isString }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0 }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0 }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0u }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0u }
notAlignedStrings.forEach {
outputStringvar(
it.name,
@@ -713,26 +776,31 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> asmgen.out("${variable.name}\t.char ?")
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ?")
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
alignVar(variable.align)
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
}
dt.isArray -> {
alignVar(variable.align)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
asmgen.out("${variable.name}\t.fill $numbytes")
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
TODO("pointers are not supported yet")
}
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun alignVar(align: Int) {
if(align > 1)
private fun alignVar(align: UInt) {
if(align > 1u)
asmgen.out(" .align ${align.toHex()}")
}
@@ -757,6 +825,7 @@ internal class ProgramAndVarsGen(
// dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
// dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
// dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
// dt.isLong -> asmgen.out("${variable.name}\t.dint $initialValue")
// dt.isFloat -> {
// if(initialValue==0) {
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
@@ -769,7 +838,7 @@ internal class ProgramAndVarsGen(
throw AssemblyError("all string vars should have been interned into prog")
}
dt.isArray -> {
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length)
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length?.toInt())
}
else -> {
throw AssemblyError("weird dt")
@@ -777,7 +846,7 @@ internal class ProgramAndVarsGen(
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, align: Int, value: StArray?, orNumberOfZeros: Int?) {
private fun arrayVariable2asm(varname: String, dt: DataType, align: UInt, value: StArray?, orNumberOfZeros: Int?) {
alignVar(align)
when {
dt.isUnsignedByteArray || dt.isBoolArray -> {
@@ -801,7 +870,7 @@ internal class ProgramAndVarsGen(
}
}
dt.isSplitWordArray -> {
if(dt.elementType().isUnsignedWord) {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
@@ -833,6 +902,16 @@ internal class ProgramAndVarsGen(
asmgen.out(" .sint " + chunk.joinToString())
}
}
dt.isLongArray -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.dint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .dint " + chunk.joinToString())
}
}
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
@@ -849,7 +928,7 @@ internal class ProgramAndVarsGen(
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null))
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
}
@@ -879,7 +958,7 @@ internal class ProgramAndVarsGen(
}
}
private fun outputStringvar(varname: String, align: Int, encoding: Encoding, value: String) {
private fun outputStringvar(varname: String, align: UInt, encoding: Encoding, value: String) {
alignVar(align)
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false)
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
@@ -907,7 +986,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
@@ -919,8 +998,15 @@ internal class ProgramAndVarsGen(
else
asmgen.asmSymbolName(addrOfSymbol)
}
else
else if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
}
}
else -> throw AssemblyError("invalid dt")
}
@@ -944,11 +1030,11 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isArray && dt.elementType().isSignedWord -> array.map {
dt.isSignedWordArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@@ -956,6 +1042,14 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
dt.isLongArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(8, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -52,7 +52,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
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 (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0 }
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0u }
require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
@@ -60,9 +60,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@@ -79,9 +79,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@@ -92,16 +92,16 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// 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 = varsDontCareWithoutAlignment.sortedByDescending { it.scopedName }
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
for (variable in sortedList) {
if(variable.dt.isIntegerOrBool) {
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@@ -20,6 +20,8 @@ internal class AnyExprAsmGen(
private val asmgen: AsmGen6502Internal
) {
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
if(expr.operator==".")
throw AssemblyError("pointer deref expression should have been handled elsewhere ${expr.position}")
when {
expr.type.isByteOrBool -> {
if(expr.left.type.isByteOrBool && expr.right.type.isByteOrBool)
@@ -40,12 +42,24 @@ internal class AnyExprAsmGen(
}
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
expr.type.isLong -> {
require(expr.left.type.isLong && expr.right.type.isLong) {
"both operands must be longs"
}
throw AssemblyError("expression should have been handled otherwise: long ${expr.operator} at ${expr.position}")
}
expr.type.isFloat -> {
require(expr.left.type.isFloat && expr.right.type.isFloat) {
"both operands must be floats"
}
return assignFloatBinExpr(expr, assign)
}
expr.type.isPointer -> {
require((expr.left.type.isPointer || expr.left.type.isUnsignedWord) && (expr.right.type.isPointer || expr.right.type.isUnsignedWord)) {
"both operands must be pointers or uwords"
}
throw AssemblyError("expression should have been handled otherwise: pointer ${expr.operator} at ${expr.position}")
}
else -> throw AssemblyError("weird expression type in assignment")
}
}
@@ -6,11 +6,12 @@ import prog8.codegen.cpu6502.AsmGen6502Internal
internal enum class TargetStorageKind {
VARIABLE,
VARIABLE, // non-pointer variable
ARRAY,
MEMORY,
REGISTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
POINTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
}
internal enum class SourceStorageKind {
@@ -32,6 +33,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val pointer: PtPointerDeref? = null,
val origAstTarget: PtAssignTarget? = null
)
{
@@ -39,13 +41,24 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
}
init {
if(register!=null && !datatype.isNumericOrBool)
throw AssemblyError("must be numeric type")
if(kind==TargetStorageKind.REGISTER)
require(register!=null)
else
require(register==null)
if(kind==TargetStorageKind.POINTER)
require(pointer!=null)
if(pointer!=null)
require(kind==TargetStorageKind.POINTER)
}
companion object {
@@ -77,6 +90,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
@@ -100,25 +114,17 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.FAC2 -> {
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
}
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
in Cx16VirtualRegisters -> {
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
in combinedLongRegisters -> {
val dt = if(signed) DataType.LONG
else
TODO("unsigned long")
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
else -> throw AssemblyError("weird register $registers")
}
}
@@ -134,11 +140,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
left is PtIdentifier && left.name==scopedName
}
TargetStorageKind.ARRAY -> {
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords && (left.pointerderef==null && array.pointerderef==null || left.pointerderef!! isSameAs array.pointerderef!!)
}
TargetStorageKind.MEMORY -> {
left isSameAs memory!!
}
TargetStorageKind.POINTER -> {
TODO("is pointer deref target same as expression? ${this.position}")
}
TargetStorageKind.REGISTER -> false
TargetStorageKind.VOID -> false
}
@@ -160,8 +169,11 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
companion object {
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
@@ -203,7 +215,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.returns.size>1)
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
File diff suppressed because it is too large. Load diff
@@ -9,6 +9,7 @@ import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen6502Internal,
private val ptrgen: PointerAssignmentsGen,
private val allocator: VariableAllocator
) {
fun translate(assign: AsmAugmentedAssignment, scope: IPtSubroutine?) {
@@ -84,26 +85,38 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isWord -> {
target.datatype.isWord || target.datatype.isPointer -> {
val block = target.origAstTarget?.definingBlock()
val targetDt = if(target.datatype.isWord) target.datatype else DataType.UWORD // pointers themselves that get a new value are just treated as UWORD variables
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.boolean!!.asInt(), block)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!, block)
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationWordWithLiteralval(target.asmVarname, targetDt, operator, value.boolean!!.asInt(), block)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, targetDt, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, targetDt, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, targetDt, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, targetDt, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression, block)
inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.expression, block)
}
else {
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!, block)
inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.expression!!, block)
}
}
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(target.asmVarname, operator, value.asmVarname)
SourceStorageKind.EXPRESSION -> inplacemodificationLongWithExpression(target.asmVarname, operator, value.expression!!)
SourceStorageKind.REGISTER -> TODO("32 bits register inplace modification? ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace modify long with array ${target.position}")
SourceStorageKind.MEMORY -> TODO("memread into long ${target.position}")
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt().toDouble())
@@ -205,7 +218,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
@@ -224,7 +237,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.ARRAY -> {
val indexNum = target.array!!.index as? PtNumber
val deref = target.array!!.pointerderef
if(deref!=null) {
TODO("inplace modification array indexed pointer deref ${target.position}")
return
}
val targetArrayVar = target.array.variable!!
val indexNum = target.array.index as? PtNumber
if (indexNum!=null) {
val index = indexNum.number.toInt()
if(target.array.splitWords) {
@@ -283,6 +302,25 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("inplace long modifiication ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modifiication ${target.position}")
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
TODO("inplace long modifiication ${target.position}")
} else {
TODO("inplace long modifiication ${target.position}")
}
}
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.boolean!!.asInt().toDouble())
@@ -301,7 +339,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isPointer -> TODO("inplace modification of pointer array ${target.position}")
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
@@ -320,7 +358,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" lda ${targetArrayVar.name},y")
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
inplacemodificationRegisterAwithVariable(operator, "#${value.boolean!!.asInt()}", target.datatype.isSigned)
@@ -356,7 +394,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
@@ -366,7 +404,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda $tempVar")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
asmgen.out(" sta ${targetArrayVar.name},y")
}
target.datatype.isWord -> {
@@ -377,11 +415,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
asmgen.out(" lda ${targetArrayVar.name}_lsb,y")
asmgen.out(" ldx ${targetArrayVar.name}_msb,y")
} else {
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
asmgen.out(" lda ${targetArrayVar.name},y")
asmgen.out(" ldx ${targetArrayVar.name}+1,y")
}
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
@@ -439,7 +477,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
@@ -450,14 +488,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(target.array.splitWords)
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
asmgen.out(" sta ${targetArrayVar.name}_lsb,y | txa | sta ${targetArrayVar.name}_msb,y")
else
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
asmgen.out(" sta ${targetArrayVar.name},y | txa | sta ${targetArrayVar.name}+1,y")
}
target.datatype.isFloat -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
@@ -504,18 +542,466 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
pla ; restore array ptr lsb
jsr floats.copy_float""")
}
target.datatype.isPointer -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
}
TargetStorageKind.POINTER -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
inplacemodificationLongWithVariable(targetVar, operator, "cx16.r0")
}
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
when(operator) {
"+" -> {
asmgen.out("""
clc
lda $targetVar
adc $sourceVar
sta $targetVar
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3""")
}
"-" -> {
asmgen.out("""
sec
lda $targetVar
sbc $sourceVar
sta $targetVar
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3""")
}
"<<" -> {
asmgen.out("""
ldy $sourceVar
- asl $targetVar
rol $targetVar+1
rol $targetVar+2
rol $targetVar+3
dey
bne -""")
}
">>" -> {
asmgen.out("""
ldy $sourceVar
- lda $targetVar+3
asl a ; save sign bit
ror $targetVar+3
ror $targetVar+2
ror $targetVar+1
ror $targetVar
dey
bne -""")
}
"|" -> {
asmgen.out("""
lda $targetVar
ora $sourceVar
sta $targetVar
lda $targetVar+1
ora $sourceVar+1
sta $targetVar+1
lda $targetVar+2
ora $sourceVar+2
sta $targetVar+2
lda $targetVar+3
ora $sourceVar+3
sta $targetVar+3""")
}
"&" -> {
asmgen.out("""
lda $targetVar
and $sourceVar
sta $targetVar
lda $targetVar+1
and $sourceVar+1
sta $targetVar+1
lda $targetVar+2
and $sourceVar+2
sta $targetVar+2
lda $targetVar+3
and $sourceVar+3
sta $targetVar+3""")
}
"^" -> {
asmgen.out("""
lda $targetVar
eor $sourceVar
sta $targetVar
lda $targetVar+1
eor $sourceVar+1
sta $targetVar+1
lda $targetVar+2
eor $sourceVar+2
sta $targetVar+2
lda $targetVar+3
eor $sourceVar+3
sta $targetVar+3""")
}
else -> {
TODO("in-place modify LONG with variable")
}
}
}
internal fun inplacemodificationLongWithLiteralval(variable: String, operator: String, value: Int) {
fun inplaceLongShiftLeft() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
asl $variable
rol $variable+1
rol $variable+2
rol $variable+3""")
}
}
value in 3..7 -> {
asmgen.out("""
ldy #$value
- asl $variable
rol $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
value == 8 -> {
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable""")
}
value in 9..15 -> {
val shift = value - 8
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable
ldy #$shift
- asl $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
value == 16 -> {
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1""")
}
value in 17..23 -> {
val shift = value-16
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1
ldy #$shift
- asl $variable+2
rol $variable+3
dey
bne -""")
}
value == 24 -> {
asmgen.out("""
lda $variable
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
value <= 31 -> {
val shift = value-24
asmgen.out("""
lda $variable
ldy #$shift
- asl a
dey
bne -
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
fun inplaceLongShiftRight() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable""")
}
}
// TODO optimize for more cases 8, 16, 24 etc but don't forget to take the sign bit into account!
value <= 31 -> {
asmgen.out("""
ldy #$value
- lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable
dey
bne -""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
when(operator) {
"+" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
inc $variable
bne +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
clc
lda $variable
adc #$value
sta $variable
bcc +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
clc
lda $variable
adc #<$value
sta $variable
lda $variable+1
adc #>$value
sta $variable+1
bcc +
inc $variable+2
bne +
inc $variable+3
+""")
} else {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
clc
lda $variable
adc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
adc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
adc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
adc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"-" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
lda $variable
bne +
dec $variable+1
bne +
dec $variable+2
bne +
dec $variable+3
+ dec $variable""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
lda $variable
sec
sbc #$value
sta $variable
bcs +
dec $variable+1
bne +
dec $variable+2
bne +
dec $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
lda $variable
sec
sbc #<$value
sta $variable
lda $variable+1
sbc #>$value
sta $variable+1
bcs +
dec $variable+2
bne +
dec $variable+3
+""")
} else {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
sec
lda $variable
sbc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
sbc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
sbc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
sbc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"<<" -> if (value > 0) inplaceLongShiftLeft()
">>" -> if (value > 0) inplaceLongShiftRight()
"|" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
ora #$${hex.substring(6,8)}
sta $variable
lda $variable+1
ora #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
ora #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
ora #$${hex.take(2)}
sta $variable+3""")
}
"&" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
and #$${hex.substring(6,8)}
sta $variable
lda $variable+1
and #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
and #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
and #$${hex.take(2)}
sta $variable+3""")
}
"^" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
eor #$${hex.substring(6,8)}
sta $variable
lda $variable+1
eor #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
eor #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
eor #$${hex.take(2)}
sta $variable+3""")
}
else -> {
TODO("inplace long $operator $value")
}
}
}
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
val arrayvar = asmgen.asmVariableName(array.variable)
val arrayVar = array.variable
if(arrayVar==null) {
TODO("indexed inc/dec on pointer ${array.position}")
return false
}
val arrayvar = asmgen.asmVariableName(arrayVar)
when {
array.type.isByte -> {
asmgen.loadScaledArrayIndexIntoRegister(array, CpuRegister.X)
@@ -571,6 +1057,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(if(operator=="+") " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
return true
}
array.type.isPointer -> TODO("indexed inc/dec on pointer ${array.position}")
else -> return false
}
}
@@ -884,7 +1371,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun tryInplaceModifyWithRemovedRedundantCast(value: PtTypeCast, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
if (target.datatype == value.type || (target.datatype.isPointer && value.type.isWord)) {
val childDt = value.value.type
if (!value.type.isFloat && (value.type.equalsSize(childDt) || value.type.largerSizeThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
@@ -910,7 +1397,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@@ -972,7 +1459,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tinc ${'$'}ffff\t; modified")
asmgen.out($$"+\tinc $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@@ -992,7 +1479,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tdec ${'$'}ffff\t; modified")
asmgen.out($$"+\tdec $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@@ -1019,7 +1506,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
@@ -1125,11 +1612,18 @@ $shortcutLabel:""")
}
if(value is PtArrayIndexer && value.isSimple()) {
val valueVar = value.variable
if(valueVar==null) {
TODO("inplace modification on pointer ${value.position}")
return
}
// use the already existing optimized codegen for regular assignments x += array[index]
val binexpr = PtBinaryExpression(operator, dt, value.position)
binexpr.add(PtIdentifier(name, dt, value.position))
val arrayValue = PtArrayIndexer(value.type, value.position)
arrayValue.add(value.variable)
arrayValue.add(valueVar)
arrayValue.add(value.index)
binexpr.add(arrayValue)
binexpr.parent = value
@@ -1190,7 +1684,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1363,7 +1857,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" tay | lda $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1534,7 +2028,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@@ -1829,7 +2323,7 @@ $shortcutLabel:""")
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
} else {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware mul
asmgen.out("""
lda $lsb
@@ -1840,9 +2334,10 @@ $shortcutLabel:""")
ldy #>$value
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $lsb
sty $msb""")
}
else
asmgen.out("""
lda $lsb
@@ -1921,18 +2416,16 @@ $shortcutLabel:""")
asmgen.out(" lda #0 | sta $lsb")
}
value==7 -> {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
asmgen.out("""
; shift left 7
lsr $msb
php ; save carry
lda $lsb
ror a
sta $msb
lda #0
sta $lsb
plp ; restore carry
ror $msb
ror $lsb""")
ror a
sta $lsb""")
}
value>3 -> asmgen.out("""
ldy #$value
@@ -2258,7 +2751,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType, block: PtBlock?) {
require(dt.isWord)
require(valueDt.isInteger)
require(valueDt.isInteger || valueDt.isPointer)
when {
valueDt.isByte -> {
// the other variable is a BYTE type so optimize for that
@@ -2329,9 +2822,10 @@ $shortcutLabel:""")
ldy $name+1
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
} else {
if(valueDt.isUnsignedByte) {
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
@@ -2468,13 +2962,13 @@ $shortcutLabel:""")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
// the value is a proper 16-bit word, so use both bytes of it.
when (operator) {
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
"*" -> {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
lda $name
@@ -2485,9 +2979,10 @@ $shortcutLabel:""")
ldy $otherName+1
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
lda $otherName
@@ -2678,7 +3173,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
require(dt.isWord)
fun multiplyVarByWordInAX() {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
@@ -2687,9 +3182,10 @@ $shortcutLabel:""")
ldx $name+1
sta cx16.r0
stx cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
sta prog8_math.multiply_words.multiplier
@@ -2890,7 +3386,7 @@ $shortcutLabel:""")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
// the value is a proper 16-bit word, so use both bytes of it.
if(value is PtArrayIndexer && value.isSimple()) {
@@ -2905,7 +3401,12 @@ $shortcutLabel:""")
"-" -> {
if(value.index.type.isByte) {
// it's an array indexed by a byte so we can use sbc array,y
val arrayname = value.variable.name
val valueVar = value.variable
if(valueVar==null) {
TODO("inplace modification on pointer ${value.position}")
return
}
val arrayname = valueVar.name
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
asmgen.out("""
@@ -2978,7 +3479,7 @@ $shortcutLabel:""")
if(value is PtNumber && value.number<=255) {
TODO("shift a word var by ${value.number}")
} else {
throw AssemblyError("shift by a word value not supported, max is a byte")
throw AssemblyError("bit shift value can not be larger than a byte")
}
}
"&" -> {
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -5,17 +5,21 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray) {
require(numElements != null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
+1
View File
@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
+1 -3
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -11,7 +9,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:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {
+1 -5
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -11,12 +9,10 @@ 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:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
sourceSets {
+1 -2
View File
@@ -13,9 +13,8 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<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" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
File diff suppressed because it is too large. Load diff
@@ -1,8 +1,12 @@
package prog8.codegen.intermediate
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.intermediate.*
@@ -10,7 +14,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return when(call.name) {
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
@@ -20,22 +24,28 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
"call" -> funcCall(call)
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
"msw" -> funcMsw(call)
"lsw" -> funcLsw(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekl" -> funcPeek(call, IRDataType.LONG)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokebool" -> funcPoke(call, IRDataType.BYTE)
"pokebowl" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokel" -> funcPoke(call, IRDataType.LONG)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
"mkword" -> funcMkword(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"mklong", "mklong2" -> funcMklong(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
@@ -45,6 +55,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -194,6 +207,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
}
BaseDataType.LONG -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
@@ -243,6 +267,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
BaseDataType.LONG -> TODO("SQRT LONG")
BaseDataType.FLOAT -> {
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
@@ -273,6 +298,52 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(IRDataType.LONG)
if(call.args.size==2) {
// mklong2(word, word)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msw is 0, use EXT
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
} else {
val mswTr = exprGen.translateExpression(call.args[0])
addToResult(result, mswTr, mswTr.resultReg, -1)
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
}
} else {
// mklong(msb, b3, b2, lsb)
if((call.args[0] as? PtNumber)?.number == 0.0 && (call.args[1] as? PtNumber)?.number == 0.0 && (call.args[2] as? PtNumber)?.number == 0.0) {
// use EXT.b + EXT.w
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val wordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=wordReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = wordReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
val b2Tr = exprGen.translateExpression(call.args[1])
val b1Tr = exprGen.translateExpression(call.args[2])
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, msbTr, msbTr.resultReg, -1)
addToResult(result, b2Tr, b2Tr.resultReg, -1)
addToResult(result, b1Tr, b1Tr.resultReg, -1)
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val lswReg = codeGen.registers.next(IRDataType.WORD)
val mswReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
}
}
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val type = irType(call.type)
@@ -297,6 +368,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
result += codeGen.makeSyscall(syscall, listOf(
@@ -493,8 +565,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.BYTE, resultReg, -1)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcStructAlloc(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -507,6 +587,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
@@ -517,6 +606,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val arg = call.args[0]
@@ -544,7 +642,9 @@ 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) {
val variable = arr.variable.name
if(arr.variable==null)
TODO("support for ptr indexing ${arr.position}")
val variable = arr.variable!!.name
if(arr.splitWords) {
result += IRCodeChunk(null, null).also {
when(opcodeMemAndReg.first) {
@@ -613,7 +713,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(target.variable==null)
TODO("support for ptr indexing ${target.position}")
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
@@ -647,6 +749,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
else {
val targetVariable = target.variable ?: TODO("support for ptr indexing ${target.position}")
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
@@ -655,17 +759,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
} else {
@@ -676,17 +780,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
}
@@ -2,10 +2,7 @@ package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.Statusflag
import prog8.code.core.*
import prog8.intermediate.*
@@ -86,15 +83,111 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtArrayIndexer -> translate(expr)
is PtBinaryExpression -> translate(expr)
is PtIfExpression -> translate(expr)
is PtBranchCondExpression -> translate(expr)
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
is PtFunctionCall -> translate(expr)
is PtContainmentCheck -> translate(expr)
is PtPointerDeref -> translate(expr)
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
}
}
private fun translate(deref: PtPointerDeref): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
var pointerReg: Int
if(deref.startpointer.type.isStructInstance) {
TODO("translate structinstance deref???")
/*
val arrayIndexer = deref.startpointer as? PtArrayIndexer
if(arrayIndexer==null)
throw AssemblyError("when start pointer is struct instance, array indexer is expected PTR[x]")
// first evaluate the adress of POINTER[x].a
// which is: value in pointer + x*sizeof(struct) + offsetof(a)
// then use traverseDerefChainToCalculateFinalAddress on b.c.d.field
val struct = deref.startpointer.type.subType as StStruct
val chain = ArrayDeque(deref.chain)
val firstField = struct.getField(chain.removeFirst(), codeGen.program.memsizer)
val pointerTr = translateExpression(arrayIndexer.variable)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
val constIndex = arrayIndexer.index.asConstInteger()
if(constIndex!=null) {
val offset = constIndex * struct.size.toInt()
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = offset), null)
} else {
val indexTr = translateExpression(arrayIndexer.index)
result += indexTr.chunks
// multiply the index by the size of the struct and add that to the pointer, then add the offset of the field,
// and retrieve the pointer value that is stored there, or the actual value if it's a pointer to simple type
result += IRCodeChunk(null, null).also {
val indexReg: Int
if(arrayIndexer.index.type.isByte) {
// extend array index to word
indexReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, indexReg, indexTr.resultReg)
} else {
indexReg = indexTr.resultReg
}
it += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt())
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerReg, reg2 = indexReg)
}
}
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = firstField.second.toInt())
if (firstField.first.isPointer) {
// get the address stored in the pointer and use that for the rest of the chain
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
} else {
require(chain.isEmpty())
// it's a pointer to a simple value, so keep the pointer as-is
}
}
// now use traverseDerefChainToCalculateFinalAddress on b.c.d and finally field or on b.c.d.field if field isn't a field.
val derefField = if(deref.type.isPointer) null else chain.removeLastOrNull()
actualDeref = PtPointerDeref(deref.type, chain, derefField, deref.position)
actualDeref.add(arrayIndexer.variable)
*/
} else {
val tr = translateExpression(deref.startpointer)
result += tr.chunks
pointerReg = tr.resultReg
}
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
result += instructions
if(offset<=0u) {
val irdt = irType(deref.type)
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
// load field with offset
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val irdt = irType(deref.type)
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADFIELD, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
private fun translate(ifExpr: PtIfExpression): ExpressionCodeResult {
if((ifExpr.condition as? PtPrefix)?.operator=="not")
@@ -161,21 +254,83 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translate(branchExpr: PtBranchCondExpression): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val trueTr = translateExpression(branchExpr.truevalue)
val falseTr = translateExpression(branchExpr.falsevalue)
val trueLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
val irDt = irType(branchExpr.type)
if(branchExpr.condition==BranchCondition.CC && irDt==IRDataType.BYTE) {
if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
}
else if(branchExpr.condition==BranchCondition.CS) {
if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
}
val branchInstr = codeGen.IRBranchInstr(branchExpr.condition, trueLabel)
addInstr(result, branchInstr, null)
if (irDt != IRDataType.FLOAT) {
addToResult(result, falseTr, trueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(trueLabel, null)
addToResult(result, trueTr, trueTr.resultReg, -1)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
} else {
addToResult(result, falseTr, -1, trueTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(trueLabel, null)
addToResult(result, trueTr, -1, trueTr.resultFpReg)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, irDt, -1, trueTr.resultFpReg)
}
}
private fun translate(expr: PtAddressOf): ExpressionCodeResult {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(vmDt)
val identifier = expr.identifier
fun loadAddressOfArrayLabel(reg: Int) {
if (expr.isMsbForSplitArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_msb"), null)
} else if (expr.identifier.type.isSplitWordArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier!!.name + "_msb"), null)
} else if (identifier!!.type.isSplitWordArray) {
// the _lsb split array comes first in memory
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_lsb"), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name + "_lsb"), null)
} else
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name), null)
}
if(expr.isFromArrayElement) {
@@ -186,30 +341,55 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null)
ixWord
} else indexTr.resultReg
if(expr.identifier.type.isUnsignedWord) {
val resultRegister = codeGen.registers.next(vmDt)
if(identifier!!.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
val ptr = codeGen.symbolTable.lookup(expr.identifier.name)
val ptr = codeGen.symbolTable.lookup(identifier.name)
it += if(ptr is StConstant)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
else
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
}
} else if(identifier.type.isPointer) {
// apply pointer arithmetic for the array indexing
val eltSize = if(identifier.type.sub!=null)
codeGen.program.memsizer.memorySize(identifier.type.sub!!)
else
identifier.type.subType!!.memsize(codeGen.program.memsizer)
result += IRCodeChunk(null, null).also {
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name), null)
if (eltSize > 1) {
it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = resultRegister, reg2 = indexWordReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
// regular array indexing
val eltSize = codeGen.program.memsizer.memorySize(identifier.type, 1)
result += IRCodeChunk(null, null).also {
loadAddressOfArrayLabel(resultRegister)
if(eltSize>1 && !expr.identifier.type.isSplitWordArray) {
it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1=indexWordReg, immediate = eltSize)
if (eltSize > 1 && !identifier.type.isSplitWordArray) {
it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = resultRegister, reg2 = indexWordReg)
}
}
} else {
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else if(expr.identifier!=null ) {
val resultRegister = codeGen.registers.next(vmDt)
loadAddressOfArrayLabel(resultRegister)
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else {
require(vmDt==IRDataType.WORD)
val pointerTr = translateExpression(expr.dereference!!.startpointer)
result += pointerTr.chunks
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg)
result += instructions
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset.toInt()), null)
return ExpressionCodeResult(result, vmDt, pointerTr.resultReg, -1)
}
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
private fun translate(mem: PtMemoryByte): ExpressionCodeResult {
@@ -223,31 +403,32 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
val ptrWithOffset = mem.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// LOADIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=offsetReg, labelSymbol = ptrName)
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.LOADFIELD, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
val tr = translateExpression(mem.address)
addToResult(result, tr, tr.resultReg, -1)
@@ -265,7 +446,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else (it as PtNumber).number.toInt()
}
when {
elementDt.isIntegerOrBool -> {
elementDt.isWordOrByteOrBool -> {
if (elementDt.isByteOrBool) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt.isWord) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
@@ -346,11 +527,30 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(arrayIx: PtArrayIndexer): ExpressionCodeResult {
if(arrayIx.type.isStructInstance)
throw AssemblyError("cannot translate POINTER[x] resulting in a struct instance; this is likely part of a larger expression POINTER[x].field and that has to be translated earlier as a whole")
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type, null)
val vmDt = irType(arrayIx.type)
val result = mutableListOf<IRCodeChunkBase>()
val arrayVarSymbol = arrayIx.variable.name
val arrayVar = arrayIx.variable
if(arrayVar==null) {
val pointerTr = translateExpression(arrayIx.pointerderef!!)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
return translatePointerIndexing(result, pointerReg, arrayIx.index, eltSize, vmDt)
}
if(arrayVar.type.isPointer) {
val pointerTr = translateExpression(arrayVar)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
return translatePointerIndexing(result, pointerReg, arrayIx.index, eltSize, vmDt)
}
require(!arrayVar.type.isPointer) { "only regular array indexing here ${arrayIx.position}" }
var resultRegister = -1
var resultFpRegister = -1
val arrayVarSymbol = arrayVar.name
if(arrayIx.splitWords) {
require(vmDt==IRDataType.WORD)
@@ -377,9 +577,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
}
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize)
fun indexByNumber(index: Int) {
val memOffset = index * eltSize
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
@@ -388,23 +587,61 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
}
fun indexByExpression() {
val (code, indexByteReg) = codeGen.loadIndexReg(arrayIx.index, eltSize, false, arrayIx.splitWords)
result += code
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexByteReg, labelSymbol = arrayVarSymbol), null)
}
}
if(arrayIx.index is PtNumber)
indexByNumber((arrayIx.index as PtNumber).number.toInt())
else
indexByExpression()
return ExpressionCodeResult(result, vmDt, resultRegister, resultFpRegister)
}
private fun translatePointerIndexing(
result: MutableList<IRCodeChunkBase>,
pointerReg: Int,
index: PtExpression,
eltSize: Int,
resultDt: IRDataType
): ExpressionCodeResult {
var resultRegister = -1
var resultFpRegister = -1
if(index is PtNumber) {
val memOffset = eltSize * index.number.toInt()
if(memOffset>0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1=pointerReg, immediate = memOffset), null)
}
else {
val (code, indexWordReg) = codeGen.loadIndexReg(index, eltSize, true, false)
result += code
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=pointerReg, reg2=indexWordReg), null)
}
if(resultDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
}
else {
resultRegister = codeGen.registers.next(resultDt)
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg), null)
}
return ExpressionCodeResult(result, resultDt, resultRegister, resultFpRegister)
}
private fun translate(expr: PtPrefix): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(expr.value)
@@ -445,10 +682,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 =loadStatusAsBooleanResult(Opcode.BSTNE, result)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isLong -> TODO("typecast long ${cast.position}")
valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
@@ -456,7 +694,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=actualResultReg2, immediate = 1)
}
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.UBYTE -> {
@@ -468,11 +706,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> TODO("cast UBYTE to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.BYTE -> {
@@ -484,11 +723,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> TODO("cast BYTE to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.UWORD -> {
@@ -506,11 +746,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> TODO("cast UWORD to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
BaseDataType.POINTER -> {
actualResultReg2 = tr.resultReg
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.WORD -> {
@@ -528,11 +772,39 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.UWORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> TODO("cast WORD to LONG ${cast.position}}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.LONG -> {
when(valueDt.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> {
// ubyte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.BYTE -> {
// byte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.UWORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
BaseDataType.WORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.FLOAT -> {
@@ -550,49 +822,61 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
else -> throw AssemblyError("weird cast type")
BaseDataType.POINTER -> {
require(valueDt.isUnsignedWord || valueDt.isPointer)
actualResultReg2 = tr.resultReg
// no further conversion required, pointers are all just uwords
}
BaseDataType.ARRAY_POINTER -> {
TODO("typecast to array of pointers $valueDt -> ${cast.type}")
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
return ExpressionCodeResult(result, irType(cast.type), actualResultReg2, actualResultFpReg2)
}
private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult {
val vmDt = irType(binExpr.left.type)
val signed = binExpr.left.type.isSigned
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
"!=" -> operatorEquals(binExpr, vmDt, true)
"<" -> operatorLessThan(binExpr, vmDt, signed, false)
">" -> operatorGreaterThan(binExpr, vmDt, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, signed, true)
else -> throw AssemblyError("weird operator ${binExpr.operator}")
if(binExpr.operator==".") {
return operatorDereference(binExpr) // eww, nasty, would rather not have any such expressions anymore
} else {
val vmDt = irType(binExpr.left.type)
return when (binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
"!=" -> operatorEquals(binExpr, vmDt, true)
"<" -> operatorLessThan(binExpr, vmDt, signed, false)
">" -> operatorGreaterThan(binExpr, vmDt, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, signed, true)
else -> throw AssemblyError("weird operator ${binExpr.operator}")
}
}
}
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
if(callTarget.scopedNameString in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
// 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) {
when(callTarget.scopedNameString) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLC), null)
@@ -641,7 +925,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
// return value(s)
// TODO: for current implemenation of the call convention in case of multiple return values,
// TODO: for current implementation of the call convention in case of multiple return values,
// a list of Ir virtual registers to hold the results is NOT correct (they're loaded into AY + R15..R0 instead!)
// So we use an empty list to avoid confusion here. This may change in a future version.
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
@@ -772,6 +1056,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
}
is StStruct -> {
throw AssemblyError("stray struct constructor should have been removed (normally it can only occur as initialization expression for a pointer variable)")
}
else -> {
if(callTarget.type == StNodeType.LABEL) {
require(fcall.void)
@@ -781,7 +1068,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
else {
throw AssemblyError("invalid node type")
throw AssemblyError("invalid node type ${callTarget.type} at ${callTarget.astNode?.position}")
}
}
}
@@ -814,7 +1101,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
val chunk = mutableListOf<IRCodeChunkBase>()
when(callTarget.scopedName) {
when(callTarget.scopedNameString) {
"sys.push" -> {
// push byte
val tr = translateExpression(fcall.args.single())
@@ -1049,7 +1336,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@@ -1064,7 +1351,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@@ -1224,7 +1511,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDivide(binExpr: PtBinaryExpression, vmDt: IRDataType, signed: Boolean): ExpressionCodeResult {
private fun operatorDivide(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==IRDataType.FLOAT) {
@@ -1239,11 +1527,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, if(signed)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
, null)
addInstr(result, if(dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
, null)
return ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@@ -1251,35 +1539,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, signed)
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, dt.isSigned)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(binExpr.right is PtNumber) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
addInstr(result, if (signed)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
, null)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
, null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, if (signed)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
, null)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
, null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}
}
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorMultiply(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorLeft = binExpr.left as? PtNumber
val constFactorRight = binExpr.right as? PtNumber
@@ -1301,7 +1590,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@@ -1309,20 +1598,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorLeft.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
val opcode = if(dt.isSigned) Opcode.MULSR else Opcode.MULR
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
@@ -1438,6 +1728,136 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDereference(binExpr: PtBinaryExpression): ExpressionCodeResult {
// the only case we support here is: a.b.c[i] . value
val left = binExpr.left as? PtArrayIndexer
val right = binExpr.right as? PtIdentifier
require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"}
val result = mutableListOf<IRCodeChunkBase>()
val field: Pair<DataType, UByte>
val pointerReg: Int
var extraFieldOffset = 0
if(left.type.isStructInstance) {
// indexing on a pointer directly
// fetch pointer address, determine struct and field, add index * structsize
if(left.variable!=null) {
val pointerTr = translateExpression(left.variable!!)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
} else if(left.pointerderef!=null) {
TODO("get pointer from deref $left")
} else {
throw AssemblyError("weird arrayindexer $left")
}
val struct = left.type.subType!! as StStruct
val constindex = left.index as? PtNumber
if(constindex!=null) {
extraFieldOffset = struct.size.toInt() * constindex.number.toInt()
} else {
val (chunks, indexReg) = codeGen.loadIndexReg(left.index, struct.size.toInt(), true, false)
result += chunks
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerReg, reg2 = indexReg), null)
}
field = struct.getField(right.name, codeGen.program.memsizer)
} else {
// indexing on an array with pointers
// fetch the pointer from the array, determine the struct & field
val indexedTr = translateExpression(left)
result += indexedTr.chunks
pointerReg = indexedTr.resultReg
val struct = left.type.dereference().subType as? StStruct
require(indexedTr.dt == IRDataType.WORD && struct != null)
field = struct.getField(right.name, codeGen.program.memsizer)
}
// add field offset to pointer and load the value into the result register
val fieldVmDt = irType(field.first)
val fieldOffset = field.second.toInt() + extraFieldOffset
var resultFpReg = -1
var resultReg = -1
if (fieldVmDt == IRDataType.FLOAT)
resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
else
resultReg = codeGen.registers.next(fieldVmDt)
if(fieldOffset==0) {
// no offset, can use current pointer directly
result += IRCodeChunk(null, null).also {
it += if (fieldVmDt == IRDataType.FLOAT)
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
}
} else {
// actually have to add an offset
if(fieldOffset<=255) {
if(fieldVmDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, fpReg1 = resultFpReg, reg1 = pointerReg, immediate = fieldOffset), null)
else
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null)
} else {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldOffset)
it += if (fieldVmDt == IRDataType.FLOAT)
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
}
}
}
return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg)
}
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, UByte> {
// returns instructions to calculate the pointer address, and the offset into the struct it points to
// so that LOADFIELD and STOREFIELD opcodes can be used instead of having an explicit extra ADD
val result = mutableListOf<IRCodeChunkBase>()
if(targetPointerDeref.chain.isEmpty())
return result to 0u // nothing to do; there's no deref chain
var struct: StStruct? = null
if(targetPointerDeref.startpointer.type.subType!=null)
struct = targetPointerDeref.startpointer.type.subType as StStruct
if(targetPointerDeref.chain.isNotEmpty()) {
// traverse deref chain
for(deref in targetPointerDeref.chain.dropLast(1)) {
val fieldinfo = struct!!.getField(deref, codeGen.program.memsizer)
struct = fieldinfo.first.subType as StStruct
// get new pointer from field
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
}
}
}
val field = targetPointerDeref.chain.last()
val fieldinfo = struct!!.getField(field, codeGen.program.memsizer)
if(fieldinfo.second>0u) {
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
return result to 0u
} else {
return result to fieldinfo.second
}
}
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
}
return result to 0u
}
}
@@ -47,6 +47,7 @@ class IRCodeGen(
ensureFirstChunkLabels(irProg)
irProg.linkChunks()
irProg.convertAsmChunks()
irProg.splitSSAchunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
@@ -56,6 +57,8 @@ class IRCodeGen(
return irProg
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
// Normally, block level (global) variables that have a numeric initialization value
// are initialized via an assignment statement.
@@ -65,17 +68,16 @@ class IRCodeGen(
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
val block = variable.parent.astNode as PtBlock
val initialization = (block.children.firstOrNull {
it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedName
it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedNameString
} as PtAssignment?)
val initValue = initialization?.value
when(initValue){
when(val initValue = initialization?.value){
is PtBool -> {
require(initValue.asInt()!=0) { "boolean var should not be initialized with false, it wil be set to false as part of BSS clear, initializer=$initialization" }
require(initValue.asInt()!=0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
require(initValue.number!=0.0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}
@@ -104,6 +106,15 @@ class IRCodeGen(
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtPointerDeref -> require('.' in node.startpointer.name) { "node $node name is not scoped: ${node.startpointer.name}" }
is PtIdentifier -> {
if('.' !in node.name) {
// there is 1 case where the identifier is not scoped: if it's the value field name after a pointer array indexing.
val expr = node.parent as? PtBinaryExpression
if (expr?.operator != "." || expr.right !== node || expr.left !is PtArrayIndexer || (!expr.left.type.isPointer && !expr.left.type.isStructInstance))
require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
}
}
else -> { /* node has no name or is ok to have no dots in the name */ }
}
node.children.forEach { verifyPtNode(it) }
@@ -256,9 +267,11 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node $node")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened $node")
is PtStructDecl -> emptyList()
is PtSubSignature -> emptyList()
else -> TODO("missing codegen for $node")
}
@@ -324,7 +337,7 @@ class IRCodeGen(
return result
}
private fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
internal fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
if(label!=null)
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
@@ -420,8 +433,9 @@ class IRCodeGen(
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
require(choice.parent.children.last() === choice)
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
@@ -433,22 +447,30 @@ class IRCodeGen(
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
val branchLabel: String
if(onlyJumpLabel==null) {
choices.add(choiceLabel to choice)
branchLabel = choiceLabel
} else {
branchLabel = onlyJumpLabel
}
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel)
}
}
}
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
if(choices.isNotEmpty())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
if(!choice.isOnlyGotoOrReturn())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
}
@@ -468,11 +490,12 @@ class IRCodeGen(
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val elementDt = irType(iterable.type.elementType())
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@@ -480,9 +503,9 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
@@ -491,10 +514,9 @@ class IRCodeGen(
result += jumpChunk
result += IRCodeChunk(endLabel, null)
}
iterable.type.isSplitWordArray -> {
iterable.type.isSplitWordArray || iterable.type.isPointerArray -> {
// iterate over lsb/msb split word array
val elementDt = iterable.type.elementType()
if(!elementDt.isWord)
if(elementDt!=IRDataType.WORD)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@@ -504,7 +526,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
@@ -548,7 +570,7 @@ class IRCodeGen(
val step = iterable.step.number.toInt()
if (step==0)
throw AssemblyError("step 0")
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@@ -634,7 +656,7 @@ class IRCodeGen(
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
val loopLabel = createLabelName()
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@@ -771,7 +793,7 @@ class IRCodeGen(
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
@@ -789,38 +811,40 @@ class IRCodeGen(
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
}
return code
}
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
internal fun multiplyByConst(dt: DataType, reg: Int, factor: Int): IRCodeChunk {
val irdt = irType(dt)
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
code += IRInstruction(Opcode.LSL, irdt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
code += IRInstruction(Opcode.LSLN, irdt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = factor)
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
IRInstruction(opcode, irdt, reg1=reg, immediate = factor)
}
}
return code
}
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
internal fun multiplyByConstInplace(dt: IRDataType, signed: Boolean, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
@@ -850,10 +874,11 @@ class IRCodeGen(
else {
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
@@ -1653,7 +1678,7 @@ class IRCodeGen(
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtTypeCast -> {
require(cond.type.isBool && cond.value.type.isNumeric)
require(cond.type.isBool && (cond.value.type.isNumeric || cond.value.type.isPointer))
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
@@ -1669,6 +1694,9 @@ class IRCodeGen(
is PtBinaryExpression -> {
translateBinExpr(cond)
}
is PtPointerDeref -> {
translateSimple(cond, Opcode.BSTEQ, false)
}
else -> throw AssemblyError("weird if condition ${ifElse.condition}")
}
return result
@@ -1743,9 +1771,7 @@ class IRCodeGen(
private fun isIndirectJump(jump: PtJump): Boolean {
if(jump.target.asConstInteger()!=null)
return false
val identifier = jump.target as? PtIdentifier
if(identifier==null)
return true
val identifier = jump.target as? PtIdentifier ?: return true
val symbol = symbolTable.lookup(identifier.name)
return symbol?.type==StNodeType.MEMVAR || symbol?.type==StNodeType.STATICVAR
}
@@ -1836,7 +1862,7 @@ class IRCodeGen(
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtAlign -> TODO("ir support for inline %align")
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returns, child.position)
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
for (subchild in child.children) {
translateNode(subchild).forEach { sub += it }
}
@@ -1885,21 +1911,22 @@ class IRCodeGen(
}
}
}
is PtStructDecl -> { /* do nothing, should be found in the symbol table */ }
else -> TODO("weird block child node $child")
}
}
return irBlock
}
private fun translate(parameters: List<PtSubroutineParameter>): List<IRSubroutine.IRParam> {
private fun translateParameters(parameters: List<PtNode>): List<IRSubroutine.IRParam> {
val result = mutableListOf<IRSubroutine.IRParam>()
parameters.forEach {
it as PtSubroutineParameter
if(it.register==null) {
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
result += IRSubroutine.IRParam(flattenedName, orig.dt)
require('.' in it.name) { "even parameter names should have been made fully scoped by now" }
val orig = symbolTable.lookup(it.name) as? StStaticVariable
?: TODO("fix missing lookup for: ${it.name} parameter")
result += IRSubroutine.IRParam(it.name, orig.dt)
} else {
val reg = it.register
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
@@ -1924,7 +1951,7 @@ class IRCodeGen(
internal fun isOne(expression: PtExpression): Boolean = (expression as? PtNumber)?.number==1.0 || (expression as? PtBool)?.value==true
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
internal fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
return IRCodeChunk(label, null).also {
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
@@ -1935,9 +1962,7 @@ class IRCodeGen(
}
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
internal fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
val chunk = IRCodeChunk(null, null)
when(registerOrFlag.registerOrPair) {
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
@@ -1951,13 +1976,91 @@ class IRCodeGen(
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
in combinedLongRegisters -> {
require(paramDt==IRDataType.LONG)
val startreg = registerOrFlag.registerOrPair!!.startregname()
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
}
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
else -> throw AssemblyError("unsupported register arg $registerOrFlag")
}
return chunk
}
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, UByte> {
// calculates the pointer address and returns the register it's in + remaining offset into the struct (so that LOADFIELD/STOREFIELD instructions can be used)
val pointerTr = expressionEval.translateExpression(deref.startpointer)
result += pointerTr.chunks
val (instructions, offset) = expressionEval.traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerTr.resultReg)
result += instructions
return pointerTr.resultReg to offset
}
internal fun storeValueAtPointersLocation(result: MutableList<IRCodeChunkBase>, addressReg: Int, offset: UByte, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) {
if(offset<=0u) {
val irdt = irType(type)
val instr = if(type.isFloat) {
if (valueIsZero) IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = existingValueRegister, reg1 = addressReg)
} else {
if (valueIsZero) IRInstruction(Opcode.STOREZI, irdt, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, irdt, reg1 = existingValueRegister, reg2 = addressReg)
}
addInstr(result, instr, null)
return
}
// store with field offset
var valueRegister = existingValueRegister
val irdt = irType(type)
if(valueIsZero && valueRegister<0) {
if(type.isFloat) {
valueRegister = registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = valueRegister, immediateFp = 0.0), null)
} else {
valueRegister = registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOAD, irdt, reg1 = valueRegister, immediate = 0), null)
}
}
val instr = if (type.isFloat)
IRInstruction(Opcode.STOREFIELD, IRDataType.FLOAT, fpReg1 = valueRegister, reg1 = addressReg, immediate = offset.toInt())
else
IRInstruction(Opcode.STOREFIELD, irdt, reg1 = valueRegister, reg2 = addressReg, immediate = offset.toInt())
addInstr(result, instr, null)
}
internal fun loadIndexReg(index: PtExpression, itemsize: Int, wordIndex: Boolean, arrayIsSplitWords: Boolean): Pair<IRCodeChunks, Int> {
// returns the code to load the Index into the register, which is also returned.
require(index !is PtNumber) { "index should not be a constant number here, calling code should handle that in a more efficient way" }
val result = mutableListOf<IRCodeChunkBase>()
if(wordIndex) {
val tr = expressionEval.translateExpression(index)
addToResult(result, tr, tr.resultReg, -1)
var indexReg = tr.resultReg
if(tr.dt==IRDataType.BYTE) {
indexReg = registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexReg, reg2=tr.resultReg), null)
}
result += multiplyByConst(DataType.UWORD, indexReg, itemsize)
return Pair(result, indexReg)
}
// regular byte size index value.
val byteIndexTr = expressionEval.translateExpression(index)
addToResult(result, byteIndexTr, byteIndexTr.resultReg, -1)
if(itemsize==1 || arrayIsSplitWords)
return Pair(result, byteIndexTr.resultReg)
result += multiplyByConst(DataType.UBYTE, byteIndexTr.resultReg, itemsize)
return Pair(result, byteIndexTr.resultReg)
}
}
@@ -53,6 +53,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
|| simplifyConstantReturns(chunk1, indexedInstructions)
|| removeNeedlessLoads(chunk1, indexedInstructions)
} while (changed)
}
}
@@ -159,10 +161,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
// if the previous chunk doesn't end in a SSA branching instruction, flow continues into the next chunk, so they may be joined
val lastInstruction = previous.instructions.lastOrNull()
if(lastInstruction!=null)
return lastInstruction.opcode !in OpcodesThatJump
return lastInstruction.opcode !in OpcodesThatEndSSAblock
return true
}
return false
@@ -298,16 +300,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// remove useless RETURN
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally) {
chunk.instructions.removeAt(idx)
changed = true
}
}
// replace subsequent opcodes that jump by just the first
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally) {
chunk.instructions.removeAt(idx)
changed = true
}
@@ -352,12 +354,35 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeNeedlessLoads(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
/*
load.b r2,#2
loadr.b r1,r2
jump p8_label_gen_2
*/
var changed=false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>=2 && ins.opcode in OpcodesThatBranchUnconditionally) {
val previous = indexedInstructions[idx-1].value
val previous2 = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) {
if(previous.reg2==previous2.reg1) {
chunk.instructions[idx-2] = previous2.copy(reg1=previous.reg1)
chunk.instructions.removeAt(idx-1)
changed=true
}
}
}
}
return changed
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MULS, Opcode.MOD -> {
if (ins.immediate == 1) {
chunk.instructions.removeAt(idx)
changed = true
@@ -455,4 +480,23 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
return changed
}
private fun simplifyConstantReturns(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// use a RETURNI when a RETURNR is just returning a constant that was loaded into a register just before
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode==Opcode.RETURNR) {
if(idx>0) {
val insBefore = chunk.instructions[idx-1]
if(insBefore.opcode == Opcode.LOAD && insBefore.immediate!=null) {
val constvalue = insBefore.immediate!!
chunk.instructions[idx] = IRInstruction(Opcode.RETURNI, ins.type, immediate = constvalue)
chunk.instructions.removeAt(idx-1)
changed = true
}
}
}
}
return changed
}
}
@@ -27,6 +27,9 @@ class IRUnusedCodeRemover(
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
// check if there are symbols referenced elsewhere that we should not prune (even though the rest of the block is empty)
blockVars.forEach { stVar ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
@@ -47,7 +50,50 @@ class IRUnusedCodeRemover(
}
}
val blockStructs = irprog.st.allStructDefs().filter { it.name.startsWith(prefix) }
blockStructs.forEach { struct ->
irprog.st.allStructInstances().forEach { instance ->
if(instance.structName == struct.name)
return // a struct instance is declared using this struct type
}
irprog.st.allVariables().forEach { variable ->
if(variable.dt.isPointer || variable.dt.isStructInstance)
if(struct.name == variable.dt.subType!!.scopedNameString)
return // a variable exists with the struct as (pointer) type
}
}
irprog.st.removeTree(blockLabel)
removeBlockInits(irprog, blockLabel)
}
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
if(ins.reg1!=0) {
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(ins.fpReg1!=0) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
}
}
}
private fun removeUnusedSubroutines(): Int {
@@ -7,6 +7,7 @@ internal class RegisterPool {
// everything from 99000 onwards is reserved for special purposes:
// 99000 - 99099 : WORD registers for syscall arguments and response value(s)
// 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
private var nextRegister: Int=1
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
@@ -18,6 +19,8 @@ internal class RegisterPool {
registerTypes[i] = IRDataType.WORD
for(i in 99100..99199)
registerTypes[i] = IRDataType.BYTE
for(i in 99200..99299)
registerTypes[i] = IRDataType.LONG
}
fun next(type: IRDataType): Int {
@@ -14,6 +14,12 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
StNodeType.STRUCTINSTANCE -> {
val instance = it.value as StStructInstance
val struct = sourceSt.lookup(instance.structName) as StStruct
st.add(convert(instance, struct.fields))
}
StNodeType.STRUCT -> st.add(convert(it.value as StStruct))
else -> { }
}
}
@@ -37,12 +43,23 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
return if (elt.boolean != null)
IRStArrayElement(elt.boolean, null, null)
else if(elt.number!=null)
IRStArrayElement(null, elt.number, null)
else {
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
IRStArrayElement(null, null, symbol)
}
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
if('.' in variable.name) {
return IRStStaticVariable(variable.name,
@@ -61,15 +78,16 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedName))
val target = variable.lookup(it.addressOfSymbol!!) ?:
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
val scopedName = variable.scopedName
val scopedName = variable.scopedNameString
return IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
@@ -94,7 +112,7 @@ private fun convert(variable: StMemVar): IRStMemVar {
)
} else {
val scopedName = try {
variable.scopedName
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
@@ -109,7 +127,7 @@ private fun convert(constant: StConstant): IRStConstant {
constant.name
} else {
try {
constant.scopedName
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
@@ -118,16 +136,21 @@ private fun convert(constant: StConstant): IRStConstant {
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
private fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
/*
*/
private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
+5 -1
View File
@@ -3,17 +3,21 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
noSysInit = true,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
+3 -2
View File
@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -340,7 +341,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks.size shouldBe 2
}
test("integer comparison expressions against zero") {
@@ -536,7 +537,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks.size shouldBe 2
}
test("extsub allowed in ir-codegen") {
+1 -3
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -8,7 +6,7 @@ dependencies {
implementation(project(":codeCore"))
implementation(project(":compilerAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
@@ -27,8 +27,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
if(addrOf.identifier?.inferType(program)?.isBytes==true)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier!!, parent))
}
return noModifications
}
@@ -39,7 +39,10 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=BaseDataType.LONG) {
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
// do NOT reduce the type if the target of the assignment is a long
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
}
}
@@ -85,6 +88,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val modifications = mutableListOf<IAstModification>()
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -332,15 +337,19 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val constIndex = arrayIndexedExpression.indexer.constIndex()
if (constIndex != null) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl()
if(arrayVar!=null) {
val array =arrayVar.value as? ArrayLiteral
if(array!=null) {
val value = array.value[constIndex].constValue(program)
if(value!=null) {
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
if(arrayIndexedExpression.plainarrayvar!=null) {
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(arrayVar!=null) {
val array =arrayVar.value as? ArrayLiteral
if(array!=null) {
val value = array.value[constIndex].constValue(program)
if(value!=null) {
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
}
}
}
} else if(arrayIndexedExpression.pointerderef!=null) {
TODO("constant fold pointer[i]")
}
}
}
@@ -390,9 +399,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val loopvar = forLoop.loopVar.targetVarDecl() ?: return noModifications
val stepLiteral = iterableRange.step as? NumericLiteral
require(loopvar.datatype.sub == null)
val loopvarSimpleDt = loopvar.datatype.base
when(loopvarSimpleDt) {
require(loopvar.datatype.isBasic)
when(val loopvarSimpleDt = loopvar.datatype.base) {
BaseDataType.UBYTE -> {
if(rangeFrom.type != BaseDataType.UBYTE) {
// attempt to translate the iterable into ubyte values
@@ -435,7 +443,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt issimpletype BaseDataType.LONG) {
if(valueDt issimpletype BaseDataType.LONG || decl.datatype.isLong) {
return noModifications // this is handled in the numericalvalue case
}
if(!(valueDt istype decl.datatype)) {
@@ -26,19 +26,15 @@ class VarConstantValueTypeAdjuster(
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
}
// replace variables by constants, if possible
@@ -173,7 +169,12 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = if(t1.isBytes) {
if(t1 issimpletype BaseDataType.BYTE) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
if(t1 issimpletype BaseDataType.WORD) "clamp__word" else "clamp__uword"
when {
t1 issimpletype BaseDataType.WORD -> "clamp__word"
t1 issimpletype BaseDataType.UWORD -> "clamp__uword"
t1 issimpletype BaseDataType.LONG -> "clamp__long"
else -> throw FatalAstException("clamp type")
}
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@@ -195,10 +196,12 @@ class VarConstantValueTypeAdjuster(
else
"${funcName}__ubyte"
} else if(t1.isInteger && t2.isInteger) {
replaceFunc = if(t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD)
"${funcName}__word"
else
"${funcName}__uword"
replaceFunc = when {
t1 issimpletype BaseDataType.LONG || t2 issimpletype BaseDataType.LONG -> "${funcName}__long"
t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD -> "${funcName}__word"
t1 issimpletype BaseDataType.UWORD || t2 issimpletype BaseDataType.UWORD -> "${funcName}__uword"
else -> throw FatalAstException("min/max type")
}
} else if(t1.isNumeric && t2.isNumeric) {
errors.err("min/max not supported for floats", functionCallExpr.position)
return noModifications
@@ -218,6 +221,7 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = when {
dt.isSignedByte -> "abs__byte"
dt.isSignedWord -> "abs__word"
dt.isLong -> "abs__long"
dt.isFloat -> "abs__float"
dt.isUnsignedByte || dt.isUnsignedWord -> {
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
@@ -313,41 +317,36 @@ internal class ConstantIdentifierReplacer(
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
return noModifications
try {
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
when {
cval.type.isNumericOrBool -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
}
when {
cval.type.isNumericOrBool -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
}
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
)
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
}
}
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.log2
@@ -18,8 +19,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type, typecast.implicit)
if (literal != null && typecast.type.isBasic) {
val newLiteral = literal.cast(typecast.type.base, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
@@ -33,7 +34,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program) issimpletype typecast.type) {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
@@ -49,7 +50,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
@@ -57,7 +58,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
@@ -90,6 +91,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val newExpr = applyAbsorptionLaws(expr)
if(newExpr!=null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -242,15 +245,21 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
if (rightVal?.number == 1.0) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -262,15 +271,21 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
if (rightVal?.number == 1.0) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -420,6 +435,104 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
// fun isPowerOfTwo(number: Double): Boolean {
// val intValue = number.toInt()
// return intValue > 0 && (intValue and (intValue - 1)) == 0
// }
fun isFactorOf256(number: Double): Boolean {
val intValue = number.toInt()
return intValue >= 256 && (intValue and 0xFF) == 0
}
if (leftDt.isUnsignedWord && rightVal!=null) {
if (expr.operator == ">" && rightVal.number == 255.0 || expr.operator == ">=" && rightVal.number == 256.0) {
// uword > 255 --> msb(value)!=0
// uword >= 256 --> msb(value)!=0
expr.operator = "!="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">=" && isFactorOf256(rightVal.number)) {
// uword >= $xx00 --> msb(value)>=xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = ">="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">" && isFactorOf256(rightVal.number+1)) {
// uword > $xxFF --> msb(value)>xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = ">"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && rightVal.number == 256.0 || expr.operator == "<=" && rightVal.number == 255.0) {
// uword < 256 --> msb(value)==0
// uword <= 255 --> msb(value)==0
expr.operator = "=="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && isFactorOf256(rightVal.number)) {
// uword < $xx00 --> msb(value)<xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = "<"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator=="<=" && isFactorOf256(rightVal.number+1)) {
// uword <= $xxFF --> msb(value)<=xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = "<="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
if(binexprParent?.operator!=".") {
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
if(dt.isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
}
} else if(arrayIndexedExpression.pointerderef==null) {
// possibly pointer[0].field --> pointer.field
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(target?.datatype?.isPointer==true) {
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
if(field!=null) {
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
}
}
}
}
val ptrDeref = arrayIndexedExpression.pointerderef
if(ptrDeref!=null) {
val dt = ptrDeref.inferType(program).getOrUndef()
if(dt.isPointer) {
// ptr1.ptr2[0] --> ptr1.ptr2^^
val deref = PtrDereference(ptrDeref.chain, true, ptrDeref.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, deref, parent))
}
}
}
return noModifications
}
@@ -499,7 +612,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (valueDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg.expression, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
} else {
@@ -516,7 +629,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (argDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
@@ -555,7 +668,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(functionCallExpr.target.nameInSource == listOf("mkword")) {
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
// just cast the lsb to uword
val cast = TypecastExpression(functionCallExpr.args[1], BaseDataType.UWORD, true, functionCallExpr.position)
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
@@ -711,9 +824,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// just use: msb(value) as type
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return if(leftDt.isSignedWord)
TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
TypecastExpression(msb, DataType.BYTE, true, expr.position)
else
TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else -> return null
}
@@ -839,14 +952,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
}
else -> {
@@ -887,12 +1000,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount==8) {
// shift right by 8 bits is just a byte operation: msb(X) as uword
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), BaseDataType.UWORD, true, expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
}
}
BaseDataType.WORD -> {
@@ -903,12 +1016,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount == 8) {
// shift right by 8 bits is just a byte operation: msb(X) as byte (will get converted to word later)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
return TypecastExpression(msb, DataType.BYTE, true, expr.position)
}
else if(amount > 8) {
// same as above but with residual shifts. Take care to do signed shift.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
val signed = TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
val signed = TypecastExpression(msb, DataType.BYTE, true, expr.position)
return BinaryExpression(signed, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
}
}
@@ -113,7 +113,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(identifier: IdentifierReference) {
identifier.targetStatement(program)?.let { target ->
identifier.targetStatement()?.let { target ->
val scoped = (target as INamedStatement).scopedName
val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
@@ -149,7 +149,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
when (it) {
is NumericLiteral -> it.copy()
is IdentifierReference -> {
val target = it.targetStatement(program) ?: return emptyList()
val target = it.targetStatement() ?: return emptyList()
val scoped = (target as INamedStatement).scopedName
IdentifierReference(scoped, it.position)
}
@@ -205,7 +205,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
val sub = functionCallStatement.target.targetStatement(program.builtinFunctions) as? Subroutine
return if(sub==null || !canInline(sub, functionCallStatement))
noModifications
else
@@ -213,7 +213,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty() && canInline(sub, functionCallExpr)) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
@@ -243,13 +243,13 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun canInline(sub: Subroutine, fcall: IFunctionCall): Boolean {
if(!sub.inline)
if (!sub.inline)
return false
if(options.compTarget.name!=VMTarget.NAME) {
if (options.compTarget.name != VMTarget.NAME) {
val stmt = sub.statements.single()
if (stmt is IFunctionCall) {
val existing = (fcall as Node).definingScope.lookup(stmt.target.nameInSource.take(1))
return existing !is VarDecl
return existing !is VarDecl && existing !is StructFieldRef
}
}
return true
@@ -19,7 +19,7 @@ class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
if("ignore_unused" !in parent.definingBlock.options())
errors.info("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@@ -29,7 +29,14 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
if(arg.arrayIndex==null) arg.identifier else null
if(arg.arrayIndex==null) {
if(arg.identifier!=null)
arg.identifier
else
null // struct can't have string fields so nothing to look at here
} else {
null
}
} else {
arg as? IdentifierReference
}
@@ -82,12 +89,11 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
)
}
@@ -106,7 +112,7 @@ class StatementOptimizer(private val program: Program,
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
@@ -146,8 +152,8 @@ class StatementOptimizer(private val program: Program,
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -161,8 +167,8 @@ class StatementOptimizer(private val program: Program,
// loop over string of length 1 -> just assign the single character
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@@ -173,9 +179,9 @@ class StatementOptimizer(private val program: Program,
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignTarget(forLoop.loopVar, null, null, null, false, position = 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))
@@ -342,6 +348,39 @@ class StatementOptimizer(private val program: Program,
}
}
// pointer arithmetic for 6502 target
if (options.compTarget.cpu != CpuType.VIRTUAL) {
if(bexpr.operator=="+" && !bexpr.right.isSimple && !assignment.isAugmentable) {
if(targetIDt.isUnsignedWord || targetIDt.getOrUndef().isPointerToByte) {
val leftDt = bexpr.left.inferType(program).getOrUndef()
val rightDt = bexpr.right.inferType(program).getOrUndef()
fun setSizedValue(a: Assignment, value: Expression, size: Int) {
val sized = BinaryExpression(value, "*", NumericLiteral.optimalInteger(size, value.position), value.position)
a.value = sized
sized.linkParents(a)
}
if (leftDt.isPointer && !leftDt.isPointerToByte) {
// uword x = pointer + value --> x=value * sizeof , x += pointer
val size = leftDt.size(options.compTarget)
setSizedValue(assignment, bexpr.right, size)
val pointerAdd = BinaryExpression(assignment.target.toExpression(), bexpr.operator, bexpr.left, bexpr.position)
val a2 = Assignment(assignment.target.copy(), pointerAdd, assignment.origin, assignment.position)
return listOf(IAstModification.InsertAfter(assignment, a2, parent as IStatementContainer))
} else if (rightDt.isPointer && !rightDt.isPointerToByte) {
// uword x = value + pointer --> x=value * sizeof, x += pointer
val size = rightDt.size(options.compTarget)
setSizedValue(assignment, bexpr.left, size)
assignment.linkParents(parent)
val pointerAdd = BinaryExpression(assignment.target.toExpression(), bexpr.operator, bexpr.right, bexpr.position)
val a2 = Assignment(assignment.target.copy(), pointerAdd, assignment.origin, assignment.position)
return listOf(IAstModification.InsertAfter(assignment, a2, parent as IStatementContainer))
}
}
}
}
}
// word = lsb(word)
@@ -466,7 +505,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
@@ -501,6 +540,27 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
} else {
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
}
}
}
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
}
return noModifications
}
@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.compiler.CallGraph
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
@@ -181,14 +181,20 @@ 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) {
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(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
val callStruct = (assignment.value as IFunctionCall).target.targetStructDecl()
if(callStruct!=null) {
// don't turn a struct instance allocation call to a void call, instead, remove everything
return listOf(IAstModification.Remove(assignment, assignment.parent as IStatementContainer))
} else {
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(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
}
} else {
+5 -7
View File
@@ -1,10 +1,9 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("application")
kotlin("jvm")
// id("com.github.johnrengelman.shadow") version "8.1.1"
id("io.github.goooler.shadow") version "8.1.8"
// id("io.github.goooler.shadow") version "8.1.8"
id("com.gradleup.shadow") version "9.2.2"
id("com.peterabeles.gversion") version "1.10.3"
}
@@ -18,18 +17,15 @@ dependencies {
implementation(project(":codeGenExperimental"))
implementation(project(":virtualmachine"))
// implementation(project(":beanshell"))
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:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
testImplementation(project(":codeCore"))
testImplementation(project(":intermediate"))
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.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
configurations.all {
@@ -83,6 +79,8 @@ gversion {
classPackage = "prog8.buildversion"
className = "Version"
language = "kotlin"
debug = false
annotate = true
}
tasks.build {
+1 -3
View File
@@ -13,9 +13,7 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<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" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="compilerAst" />
@@ -25,6 +23,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
+82
View File
@@ -0,0 +1,82 @@
bcd {
; Decimal addition an subtraction routines.
; For CPUs that support BCD mode (binary coded decimal) (not all 6502 variants support this mode...)
; This is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string. Just print the hex representation.
sub addb(byte a, byte b) -> byte {
setbcd()
a += b
clearbcd()
return a
}
sub addub(ubyte a, ubyte b) -> ubyte {
setbcd()
a += b
clearbcd()
return a
}
sub addw(word a, word b) -> word {
setbcd()
a += b
clearbcd()
return a
}
sub adduw(uword a, uword b) -> uword {
setbcd()
a += b
clearbcd()
return a
}
sub addl(long a, long b) -> long {
setbcd()
a += b
clearbcd()
return a
}
sub subb(byte a, byte b) -> byte {
setbcd()
a -= b
clearbcd()
return a
}
sub subub(ubyte a, ubyte b) -> ubyte {
setbcd()
a -= b
clearbcd()
return a
}
sub subuw(uword a, uword b) -> uword {
setbcd()
a -= b
clearbcd()
return a
}
sub subl(long a, long b) -> long {
setbcd()
a -= b
clearbcd()
return a
}
inline asmsub setbcd() {
%asm {{
php
sei
sed
}}
}
inline asmsub clearbcd() {
%asm {{
plp
}}
}
}
+216 -45
View File
@@ -133,6 +133,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
@@ -317,10 +340,25 @@ c128 {
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
extsub $FF6E = JSRFAR()
extsub $FF68 = SETBNK(ubyte databank @A, ubyte filenamebank @X)
extsub $FF47 = SPIN_SPOUT() clobbers(A) ; set up serial bus for fast communications mode
extsub $FF4A = CLOSE_ALL(ubyte device @X) clobbers(X) ; close all channels to specific device
extsub $FF4D = C64_MODE() ; restart machine in C64 mode (does not return)
extsub $FF50 = DMA_CALL(ubyte bank @X, ubyte command @Y) clobbers(A,X) ; send a command to a DMA device
extsub $FF53 = BOOT_CALL(ubyte device @X, ubyte drive @A) clobbers(A,X,Y) ; try to autoboot the given disk
extsub $FF56 = PHOENIX() clobbers(A,X,Y) ; search for and autostart ROMs, cartridges, then default disk
extsub $FF59 = LKUPLA(ubyte lfn @A) -> bool @Pc, ubyte @X ; look up logical file number to see if it's open; returns device
extsub $FF5C = LKUPSA(ubyte sa @Y) -> bool @Pc, ubyte @A, ubyte @X ; look up secondary address to see if it's in use; returns lfn and device
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column)
extsub $FF62 = DLCHR() clobbers(A,X,Y) ; copy character ROM into VDC video RAM
extsub $FF65 = PFKEY(ubyte zpaddr @A, ubyte key @X, ubyte length @Y) ; redefine programmable function key (string descriptor in zp, addr in A)
extsub $FF68 = SETBNK(ubyte data_bank @A, ubyte filename_bank @X) ; set memory bank for load/save
extsub $FF6B = GETCFG(ubyte bank @X) -> ubyte @A ; translate bank number to MMU configuration register value
extsub $FF6E = JSRFAR() clobbers(A,X) ; call routine in another bank (parameters set in zero page addresses 2-8)
extsub $FF71 = JMPFAR() clobbers(A,X) ; jump without return to another bank (parameters set as for JSRFAR)
extsub $FF74 = INDFET(ubyte zpaddr @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> ubyte @A ; fetch byte from another bank (address in zp, ptr in A)
extsub $FF77 = INDSTA(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) ; store byte to another bank (address in zp, ptr in $02b9)
extsub $FF7A = INDCMP(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> bool @Pz, bool @Pc, bool @Pv; compare byte in another bank (address in zp, ptr in $02c8)
extsub $FF7D = PRIMM() ; print immediate string
; ---- C128 specific system utility routines: ----
@@ -419,12 +457,14 @@ sys {
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
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
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -489,6 +529,7 @@ save_SCRATCH_ZPWORD2 .word ?
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
@@ -496,7 +537,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -528,6 +569,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -537,24 +579,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; enable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -576,34 +628,46 @@ _raster_irq_handler
pla
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
@@ -663,7 +727,27 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
cpy #0
bne _larger
- cmp c64.RASTER
bne -
bit c64.SCROLY
bmi -
rts
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -946,6 +1030,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -960,6 +1056,48 @@ _no_msb_size
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -984,6 +1122,7 @@ cx16 {
&uword r14 = $1bfc
&uword r15 = $1bfe
; signed word versions
&word r0s = $1be0
&word r1s = $1be2
&word r2s = $1be4
@@ -1001,6 +1140,7 @@ cx16 {
&word r14s = $1bfc
&word r15s = $1bfe
; ubyte versions (low and high bytes)
&ubyte r0L = $1be0
&ubyte r1L = $1be2
&ubyte r2L = $1be4
@@ -1035,6 +1175,7 @@ cx16 {
&ubyte r14H = $1bfd
&ubyte r15H = $1bff
; signed byte versions (low and high bytes)
&byte r0sL = $1be0
&byte r1sL = $1be2
&byte r2sL = $1be4
@@ -1069,6 +1210,42 @@ cx16 {
&byte r14sH = $1bfd
&byte r15sH = $1bff
; boolean versions (low and high bytes)
&bool r0bL = $1be0
&bool r1bL = $1be2
&bool r2bL = $1be4
&bool r3bL = $1be6
&bool r4bL = $1be8
&bool r5bL = $1bea
&bool r6bL = $1bec
&bool r7bL = $1bee
&bool r8bL = $1bf0
&bool r9bL = $1bf2
&bool r10bL = $1bf4
&bool r11bL = $1bf6
&bool r12bL = $1bf8
&bool r13bL = $1bfa
&bool r14bL = $1bfc
&bool r15bL = $1bfe
&bool r0bH = $1be1
&bool r1bH = $1be3
&bool r2bH = $1be5
&bool r3bH = $1be7
&bool r4bH = $1be9
&bool r5bH = $1beb
&bool r6bH = $1bed
&bool r7bH = $1bef
&bool r8bH = $1bf1
&bool r9bH = $1bf3
&bool r10bH = $1bf5
&bool r11bH = $1bf7
&bool r12bH = $1bf9
&bool r13bH = $1bfb
&bool r14bH = $1bfd
&bool r15bH = $1bff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
@@ -1097,12 +1274,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
-20
View File
@@ -16,26 +16,6 @@ const ubyte DEFAULT_HEIGHT = 25
extsub $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)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
chrout(7)
}
+23
View File
@@ -149,6 +149,29 @@ copy_float .proc
rts
.pend
copy_float2 .proc
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W2,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
; clobbers X
+1 -1
View File
@@ -376,7 +376,7 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 320 word x-lookup table, but hey, that's a lot of memory.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; see https://codebase64.net/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
+201 -42
View File
@@ -57,7 +57,7 @@ cbm {
; ---- CBM ROM kernal routines (C64 addresses) ----
extsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
extsub $AB1E = STROUT(str strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
extsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
extsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
extsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@@ -132,6 +132,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
@@ -313,12 +336,13 @@ asmsub banks(ubyte banks @A) {
%asm {{
and #%00000111
sta P8ZP_SCRATCH_REG
php
sei
lda $01
and #%11111000
ora P8ZP_SCRATCH_REG
sta $01
cli
plp
rts
}}
}
@@ -439,12 +463,14 @@ sys {
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
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
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -509,6 +535,7 @@ save_SCRATCH_ZPWORD2 .word ?
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
@@ -516,7 +543,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -549,6 +576,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -558,24 +586,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -598,33 +636,46 @@ _raster_irq_handler
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
@@ -685,7 +736,27 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
cpy #0
bne _larger
- cmp c64.RASTER
bne -
bit c64.SCROLY
bmi -
rts
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -968,6 +1039,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -981,6 +1064,49 @@ _no_msb_size
pla
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -1006,6 +1132,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@@ -1023,6 +1150,7 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
&ubyte r2L = $cfe4
@@ -1057,6 +1185,7 @@ cx16 {
&ubyte r14H = $cffd
&ubyte r15H = $cfff
; signed byte versions (low and high bytes)
&byte r0sL = $cfe0
&byte r1sL = $cfe2
&byte r2sL = $cfe4
@@ -1091,6 +1220,42 @@ cx16 {
&byte r14sH = $cffd
&byte r15sH = $cfff
; boolean versions
&bool r0bL = $cfe0
&bool r1bL = $cfe2
&bool r2bL = $cfe4
&bool r3bL = $cfe6
&bool r4bL = $cfe8
&bool r5bL = $cfea
&bool r6bL = $cfec
&bool r7bL = $cfee
&bool r8bL = $cff0
&bool r9bL = $cff2
&bool r10bL = $cff4
&bool r11bL = $cff6
&bool r12bL = $cff8
&bool r13bL = $cffa
&bool r14bL = $cffc
&bool r15bL = $cffe
&bool r0bH = $cfe1
&bool r1bH = $cfe3
&bool r2bH = $cfe5
&bool r3bH = $cfe7
&bool r4bH = $cfe9
&bool r5bH = $cfeb
&bool r6bH = $cfed
&bool r7bH = $cfef
&bool r8bH = $cff1
&bool r9bH = $cff3
&bool r10bH = $cff5
&bool r11bH = $cff7
&bool r12bH = $cff9
&bool r13bH = $cffb
&bool r14bH = $cffd
&bool r15bH = $cfff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
@@ -1119,12 +1284,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
-19
View File
@@ -15,25 +15,6 @@ const ubyte DEFAULT_HEIGHT = 25
extsub $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)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
; beep
+108 -10
View File
@@ -155,6 +155,30 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
}}
}
sub str_ulhex(long value) -> str {
%asm {{
lda value+3
jsr internal_ubyte2hex
sta string_out
sty string_out+1
lda value+2
jsr internal_ubyte2hex
sta string_out+2
sty string_out+3
lda value+1
jsr internal_ubyte2hex
sta string_out+4
sty string_out+5
lda value
jsr internal_ubyte2hex
sta string_out+6
sty string_out+7
lda #0
sta string_out+8
}}
return &string_out
}
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
@@ -228,15 +252,95 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
}}
}
sub str_l (long value) -> str {
; ---- convert the long value into decimal string form, without left padding 0s
; source: https://codebase64.net/doku.php?id=base:32_bit_hexadecimal_to_decimal_conversion
; NOTE: this is a pretty slow conversion, don't use for time critical displays!
bool negative
if value<0 {
negative = true
value = -value
}
%asm {{
jsr hex2dec
ldx #0
- lda result,x
bne +
inx ; skip leading zeros
cpx #9
bne -
; convert to petscii numbers and return ptr to first nonzero
+ stx P8ZP_SCRATCH_B1
inc P8ZP_SCRATCH_B1
- lda result,x
ora #$30
sta result,x
inx
cpx #10
bne -
lda #0
sta result,x
; sign needed?
lda negative
beq +
dec P8ZP_SCRATCH_B1
ldx P8ZP_SCRATCH_B1
lda #'-'
sta result_sign,x
+ lda #<result_sign
ldy #>result_sign
clc
adc P8ZP_SCRATCH_B1
bcc +
iny
+ rts
; converts 10 digits (32 bit values have max. 10 decimal digits)
hex2dec
ldx #9
l3 jsr div10
sta result,x
dex
bpl l3
rts
; divides a 32 bit value by 10
; remainder is returned in akku
div10
ldy #32 ; 32 bits
lda #0
clc
l4 rol
cmp #10
bcc skip
sbc #10
skip rol value
rol value+1
rol value+2
rol value+3
dey
bpl l4
rts
.section BSS
result_sign .byte ? ; room for the '-' sign
result .fill 10
.byte ? ; string 0 terminator
.send
}}
}
; ---- string conversion to numbers -----
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
asmsub any2uword(str string @AY) -> uword @AY, ubyte @X {
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
; (the latter two require a $ or % prefix to be recognised)
; (any non-digit character will terminate the number string that is parsed)
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
; if the string was invalid, 0 will be returned in A.
; returns the parsed number word in AY, and the number of processed characters (including the prefix symbol) in X.
; if the string was invalid, 0 will be returned as count in X (and the word value in AY will be undefined).
%asm {{
pha
sta P8ZP_SCRATCH_W1
@@ -257,13 +361,7 @@ _hex pla
_bin pla
jsr bin2uword
_result
pha
lda cx16.r15
sta P8ZP_SCRATCH_B1 ; result value
pla
sta cx16.r15
sty cx16.r15+1
lda P8ZP_SCRATCH_B1
ldx cx16.r15
rts
}}
}
+31 -37
View File
@@ -28,12 +28,20 @@
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
; - call current() to get the current task id.
; - call kill(taskid) to kill a task by id.
; - call killall() to kill all tasks.
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
;
; TIP: HOW TO WAIT without BLOCKING other coroutines?
; Make sure you call yield() in the waiting loop, for example:
; uword timer = cbm.RDTIM16() + 60
; while cbm.RDTIM16() != timer
; void coroutines.yield()
coroutines {
%option ignore_unused
@@ -41,19 +49,17 @@ coroutines {
const ubyte MAX_TASKS = 64
uword[MAX_TASKS] tasklist
uword[MAX_TASKS] userdatas
uword[MAX_TASKS] returnaddresses
ubyte active_task
uword supervisor
sub add(uword taskaddress, uword userdata) -> ubyte {
sub add(uword @nozp taskaddress, uword @nozp userdata) -> ubyte {
; find the next empty slot in the tasklist and stick it there
; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id!
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
for cx16.r0L in 0 to len(tasklist)-1 {
if tasklist[cx16.r0L] == 0 {
tasklist[cx16.r0L] = taskaddress
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
userdatas[cx16.r0L] = userdata
returnaddresses[cx16.r0L] = 0
sys.set_carry()
return cx16.r0L
}
@@ -70,57 +76,48 @@ coroutines {
}
}
sub run(uword supervisor_routine) {
sub run(uword @nozp supervisor_routine) {
supervisor = supervisor_routine
for active_task in 0 to len(tasklist)-1 {
if tasklist[active_task]!=0 {
; activate the termination handler and start the first task
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(&termination)
goto tasklist[active_task]
sys.pushw(tasklist[active_task])
return
}
}
}
sub yield() -> uword {
; Store the return address of the yielding task,
; and continue with the next one instead (round-robin)
; Returns the associated userdata value
uword task_start, task_continue
returnaddresses[active_task] = sys.popw()
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
; Returns the associated userdata value.
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
uword task_return_address
tasklist[active_task] = sys.popw()
resume_with_next_task:
skip_task:
if not next_task() {
void sys.popw() ; remove return to the termination handler
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
return 0 ; exiting here will now actually return back to the calling program that called run()
}
if supervisor!=0 {
if supervisor!=0
if lsb(call(supervisor))==0
goto resume_with_next_task
}
goto skip_task
if task_continue==0 {
; fetch start address of next task.
; address on the stack must be pushed in reverse byte order
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(task_start)
} else
sys.pushw(task_continue)
; returning from yield then continues with the next coroutine
; returning from yield then continues with the next coroutine:
sys.pushw(task_return_address)
return userdatas[active_task]
sub next_task() -> bool {
; search through the task list for the next active task
repeat len(tasklist) {
active_task++
if active_task==len(returnaddresses)
if active_task==len(tasklist)
active_task=0
task_start = tasklist[active_task]
if task_start!=0 {
task_continue = returnaddresses[active_task]
task_return_address = tasklist[active_task]
if task_return_address!=0 {
return true
}
}
@@ -128,9 +125,8 @@ resume_with_next_task:
}
}
sub kill(ubyte taskid) {
sub kill(ubyte @nozp taskid) {
tasklist[taskid] = 0
returnaddresses[taskid] = 0
}
sub current() -> ubyte {
@@ -138,12 +134,10 @@ resume_with_next_task:
}
sub termination() {
; a task has terminated. wipe it from the list.
; this is an internal routine
; internal routine: a task has terminated. wipe it from the list.
kill(active_task)
; reactivate this termination handler
; note: cannot use pushw() because JSR doesn't push the return address in the same way
; reactivate this termination handler and go to the next task
sys.push_returnaddress(&termination)
goto coroutines.yield.resume_with_next_task
goto coroutines.yield.skip_task
}
}
+2 -2
View File
@@ -19,9 +19,9 @@ bmx {
uword palette_entries ; 1-256
ubyte palette_start
ubyte compression
uword @shared palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
^^ubyte @shared palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
uword error_message ; pointer to error message, or 0 if all ok
^^ubyte error_message ; pointer to error message, or 0 if all ok
ubyte old_drivenumber
sub open(ubyte drivenumber, str filename) -> bool {
+21 -21
View File
@@ -121,7 +121,7 @@ io_error:
goto diskio.directory.internal_dir
}
sub diskname() -> uword {
sub diskname() -> str {
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
@@ -165,7 +165,7 @@ io_error:
; internal variables for the iterative file lister / loader
bool list_skip_disk_name
uword list_pattern
^^ubyte list_pattern
uword list_blocks
bool iteration_in_progress = false
bool write_iteration_in_progress = false
@@ -174,7 +174,7 @@ io_error:
; ----- get a list of files (uses iteration functions internally) -----
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
sub list_filenames(str pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
@@ -209,7 +209,7 @@ io_error:
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
sub lf_start_list(str pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
cbm.SETNAM(1, "$")
@@ -239,7 +239,7 @@ io_error:
return false
}
sub lf_start_list_dirs(uword pattern_ptr) -> bool {
sub lf_start_list_dirs(str pattern_ptr) -> bool {
; -- start an iterative directory contents listing with optional pattern matching.
; this version it only returns directory entries!
; note: only a single iteration loop can be active at a time!
@@ -247,7 +247,7 @@ io_error:
goto diskio.lf_start_list.start_list_internal
}
sub lf_start_list_files(uword pattern_ptr) -> bool {
sub lf_start_list_files(str pattern_ptr) -> bool {
; -- start an iterative directory contents listing with optional pattern matching.
; this version only returns actual file entries!
; note: only a single iteration loop can be active at a time!
@@ -267,7 +267,7 @@ io_error:
repeat {
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
^^ubyte nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
@@ -460,7 +460,7 @@ byte_read_loop: ; fallback if MACPTR isn't supported on the device
return total_read
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
asmsub f_readline(^^ubyte bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character, or 0 byte (likely EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
@@ -590,7 +590,7 @@ return_status:
; ---- other functions ----
sub status() -> uword {
sub status() -> str {
; -- retrieve the disk drive's current status message
; TODO this doesn't seem to work reliably, sometimes READST returns 128 when the drive is just fine
@@ -601,7 +601,7 @@ return_status:
; return device_not_present_error
; }
uword messageptr = &list_filename
^^ubyte messageptr = &list_filename
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
@@ -663,16 +663,16 @@ io_error:
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
sub save(str filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, false)
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
sub save_raw(str filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, true)
}
sub internal_save_routine(uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
sub internal_save_routine(str filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = startaddress + savesize
@@ -719,25 +719,25 @@ io_error:
; the return address will still be one higher. Which means, because the Kernal
; load routine is bank-aware, it will return $a000 and will have switched to the next hiram bank!
; So you'll have to reset the ram bank with cx16.rambank() to switch back to the bank that the data was put in.
sub load(uword filenameptr, uword address_override) -> uword {
sub load(str filenameptr, uword address_override) -> uword {
return internal_load_routine(filenameptr, address_override, false)
}
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details. Including the banking behavior on the X16.
sub load_raw(uword filenameptr, uword startaddress) -> uword {
sub load_raw(str filenameptr, uword startaddress) -> uword {
return internal_load_routine(filenameptr, startaddress, true)
}
; Load a prog8 compiled library binary blob at the given location into memory.
sub loadlib(uword libnameptr, uword libaddress) -> uword {
sub loadlib(str libnameptr, uword libaddress) -> uword {
return internal_load_routine(libnameptr, libaddress, true)
}
sub internal_load_routine(uword filenameptr, uword address_override, bool headerless) -> uword {
sub internal_load_routine(str filenameptr, uword address_override, bool headerless) -> uword {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
@@ -760,7 +760,7 @@ io_error:
return cx16.r1
}
sub delete(uword filenameptr) {
sub delete(str filenameptr) {
; -- delete a file on the drive
list_filename[0] = 's'
list_filename[1] = ':'
@@ -772,7 +772,7 @@ io_error:
cbm.CLOSE(1)
}
sub rename(uword oldfileptr, uword newfileptr) {
sub rename(str oldfileptr, str newfileptr) {
; -- rename a file on the drive
list_filename[0] = 'r'
list_filename[1] = ':'
@@ -786,7 +786,7 @@ io_error:
cbm.CLOSE(1)
}
sub send_command(uword commandptr) {
sub send_command(str commandptr) {
; -- send a dos command to the drive (don't read any response)
cbm.SETNAM(strings.length(commandptr), commandptr)
cbm.SETLFS(15, drivenumber, 15)
@@ -906,7 +906,7 @@ internal_vload:
send_command(list_filename)
}
sub curdir() -> uword {
sub curdir() -> str {
; return current directory name or 0 if error
; special X16 dos command to only return the current path in the entry list (R42+)
const ubyte MAX_PATH_LEN=80
+6
View File
@@ -47,6 +47,12 @@ emudbg {
}
}
sub console_nl() {
; write a newline to the debug output console.
; because '\n' gets encoded in the x16 value for it (13) which is different than what the shell expects (10).
console_chrout(10)
}
sub console_chrout(ubyte char) {
; note: make sure the character is in Iso encoding.
if is_emulator()
+2 -2
View File
@@ -21,8 +21,8 @@ extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo'
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
extsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
extsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
extsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
extsub $fe06 = FOUT() clobbers(X) -> str @ AY ; fac1 -> string, address returned in AY
extsub $fe09 = VAL_1(str string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
+1 -1
View File
@@ -825,7 +825,7 @@ set_byte:
cx16.screen_set_charset(charset, 0)
}
sub text(uword @zp xx, uword yy, ubyte color, uword sctextptr) {
sub text(uword @zp xx, uword yy, ubyte color, str sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr
+1 -1
View File
@@ -743,7 +743,7 @@ skip:
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
sub text(uword @zp xx, uword yy, ubyte color, uword textptr) {
sub text(uword @zp xx, uword yy, ubyte color, str textptr) {
; -- Write some text at the given pixel position. The text string must be in an encoding approprite for the charset.
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr
+101 -28
View File
@@ -22,18 +22,23 @@ monogfx {
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
uword buffer_visible, buffer_back
sub lores() {
; enable 320*240 bitmap mode
buffer_visible = buffer_back = $0000
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
cx16.VERA_L1_TILEBASE = 0 ; lores
width = 320
height = 240
lores_mode = true
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
@@ -46,14 +51,40 @@ monogfx {
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
cx16.VERA_L1_TILEBASE = %00000001 ; hires
width = 640
height = 480
lores_mode = false
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
sub enable_doublebuffer() {
; enable double buffering mode
if lores_mode {
buffer_visible = $0000
buffer_back = $2800
} else {
buffer_visible = $0000
buffer_back = $9800
}
}
sub swap_buffers(bool wait_for_vsync) {
; flip the buffers: make the back buffer visible and the other one now the backbuffer.
; to avoid any screen tearing it is advised to call this during the vertical blank (pass true)
if wait_for_vsync
sys.waitvsync()
cx16.r0 = buffer_back
buffer_back = buffer_visible
buffer_visible = cx16.r0
cx16.VERA_CTRL = 0
cx16.r0 &= %1111110000000000
cx16.VERA_L1_TILEBASE = cx16.VERA_L1_TILEBASE & 1 | (cx16.r0H >>1 )
}
sub textmode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
@@ -559,6 +590,7 @@ drawmode: ora cx16.r15L
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@@ -597,6 +629,7 @@ drawmode: ora cx16.r15L
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@@ -672,8 +705,7 @@ nostipple:
invert:
prepare()
%asm {{
lda cx16.VERA_DATA0
eor p8v_maskbits,y
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
}}
return
@@ -696,7 +728,7 @@ invert:
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda p8v_xx
@@ -708,18 +740,29 @@ invert:
; width=640 (hires)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx
and #7
pha ; xbits
; xx /= 8
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx
}}
xx /= 8
;xx /= 8
xx += yy*(640/8)
%asm {{
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
plx ; xbits
lda p8v_maskbits,x
}}
@@ -761,11 +804,15 @@ invert:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@@ -848,8 +895,8 @@ skip:
}
sub fill_scanline_right() {
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
while xx <= width-1 {
if pgetset()
break
@@ -884,11 +931,15 @@ skip:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xpos+1
sta cx16.VERA_ADDR_M
lda p8v_xpos
sta cx16.VERA_ADDR_L
lda p8v_xpos+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@@ -935,12 +986,12 @@ _doplot beq +
ror a
lsr a
lsr a
clc
ldy p8v_yy
clc
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00010000 ; autoincr
sta cx16.VERA_ADDR_H
@@ -948,7 +999,33 @@ _doplot beq +
}
else {
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
;cx16.r0 += xx/8
%asm {{
ldy p8v_xx+1
lda p8v_xx
sty P8ZP_SCRATCH_B1
lsr P8ZP_SCRATCH_B1
ror a
lsr P8ZP_SCRATCH_B1
ror a
lsr a
clc
adc cx16.r0
sta cx16.r0
bcc +
inc cx16.r0+1
+
stz cx16.VERA_CTRL
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.r0H
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00001000 ; autoincr (1 bit shifted)
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
}}
}
return
}
@@ -1116,15 +1193,11 @@ cdraw_mod2 ora cx16.VERA_DATA1
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
+ ldy #40
-
.rept 16
sta cx16.VERA_DATA0
.endrept
dey
bne -
rts
+1 -1
View File
@@ -283,7 +283,7 @@ palette {
; NOTE: this routine requires rom version 49+
alias set_default = cx16.set_default_palette
; get the bank and address of the word-array containing the 256 default palette colors
; get the bank and address (in A, and XY) of the word-array containing the 256 default palette colors
; NOTE: this routine requires rom version 49+
alias get_default = cx16.get_default_palette
}
Loaded 100 of 323 files, more files were not shown because too many files have changed in this diff. Show more