Compare commits

..
352 Commits
Author SHA1 Message Date
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 78b1076110 some more software links 2025-05-11 19:39:20 +02:00
Irmen de Jong 0a3c748e41 fix code gen bug for certain goto array[idx] 2025-05-11 17:18:20 +02:00
Irmen de Jong ebf79ef9e2 release 11.3.2 2025-05-11 12:30:36 +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 99b9370178 fix various bugs around word-indexing combined with address-of: &buffer[2000] 2025-05-10 16:22:05 +02:00
Irmen de Jong 506062c6b6 start implementing ptr deref augmented assigns 2025-05-09 23:05:27 +02:00
Irmen de Jong d634061cd9 Merge pull request #169 from Frosty-J/crc
Specify X16 CRC type
2025-05-08 23:47:17 +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
Frosty-J d59d8ff1fe Specify X16 CRC type 2025-05-08 05:57:30 +01: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 53e442d509 fix regression in 11.3.1: allow bitwise operation between different types as long as they're the same size. 2025-05-07 23:06:45 +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 f7cbfdff06 skip this test for now 2025-05-07 21:14:52 +02:00
Irmen de Jong b28ee0819f revert behavior change of @dirty variables, instead document the initialization behavior correctly in the docs
they get zeroed at program startup (like other uninitialized BSS variables), just not on entry in the subroutine.
2025-05-07 21:07:12 +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
Irmen de Jong 522958e0e9 @dirty variables now actually end up in the uninitialized BSS_NOCLEAR section 2025-05-06 01:41:34 +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 ccc6b56e35 added link to prog8reu library 2025-05-02 19:38:59 +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
286 changed files with 20326 additions and 5406 deletions

No files matched your search

+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>
+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)
+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 {
@@ -96,12 +96,13 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"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__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())),
"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)),
@@ -131,10 +132,15 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"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)),
"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),
+200 -56
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
@@ -50,84 +55,163 @@ val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, Bas
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)
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 -> {
@@ -148,6 +232,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 +253,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["
@@ -187,18 +304,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
@@ -214,22 +350,27 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
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 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
@@ -280,10 +421,13 @@ 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")
}
return listOf("cx16", name.lowercase()+suffix)
}
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
@@ -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,7 +7,9 @@ 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
@@ -25,7 +27,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,26 @@ 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 (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
) {
val firstVal = first.substring(4).trimStart()
val thirdVal = third.substring(4).trimStart()
if (firstVal == thirdVal)
mods.add(Modification(lines[3].index, true, null))
}
}
return mods
}
@@ -512,6 +530,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 +586,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 +712,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 +749,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 +793,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,6 @@
package prog8.codegen.cpu6502
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -46,6 +47,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peekbool" -> funcPeekBool(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokef" -> funcPokeF(fcall)
@@ -60,12 +62,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)
@@ -380,8 +384,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 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 +395,21 @@ 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 slabname = SymbolTable.labelnameForStructInstance(fcall)
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position))
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) {
@@ -415,8 +435,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 +461,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
@@ -465,7 +490,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 +526,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
@@ -521,8 +551,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 +577,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
@@ -571,7 +605,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 +640,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
@@ -633,7 +671,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
@@ -739,7 +777,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 +792,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 +851,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
}
}
@@ -815,7 +886,46 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
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 +941,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)
@@ -1112,7 +1207,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 +1226,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 +1310,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)
@@ -1276,7 +1378,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 +1392,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)
@@ -1310,7 +1412,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
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)
@@ -1328,7 +1430,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
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)
@@ -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,15 +500,15 @@ $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)
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
@@ -539,14 +539,17 @@ $endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
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) {
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
@@ -562,7 +565,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
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(
@@ -576,7 +579,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
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
@@ -586,7 +592,7 @@ $loopLabel sty $indexVar
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
@@ -602,7 +608,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
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(
@@ -616,8 +622,10 @@ $loopLabel sty $indexVar
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 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
@@ -627,16 +635,16 @@ $loopLabel sty $indexVar
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127) {
if(numElements<=127u) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
// array size is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
@@ -645,7 +653,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(length>=16) {
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(
@@ -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.isIntegerOrBool || 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
@@ -332,14 +333,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) {
@@ -38,13 +40,12 @@ internal class IfElseAsmGen(private val program: PtProgram,
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
return
}
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.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
else -> throw AssemblyError("weird dt")
}
@@ -64,6 +65,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}")
}
@@ -87,6 +98,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
@@ -94,6 +106,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
@@ -107,6 +120,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
@@ -114,6 +128,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
@@ -146,6 +161,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")
@@ -172,9 +274,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" $falseBranch +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.out("""
jmp (${target.asmLabel})
+""")
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
asmgen.out(" $branchInstr ${target.asmLabel}")
@@ -210,38 +311,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)) {
@@ -287,6 +359,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -354,6 +427,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | bne ++")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -402,13 +476,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
@@ -416,16 +490,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)
@@ -435,6 +526,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bcc + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -466,6 +558,23 @@ 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 translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -536,6 +645,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -591,6 +701,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
bcs +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
_jump jmp (${target.asmLabel})
+""")
@@ -668,6 +779,7 @@ _jump jmp (${target.asmLabel})
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -722,6 +834,7 @@ _jump jmp (${target.asmLabel})
bcc +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -784,7 +897,9 @@ _jump jmp (${target.asmLabel})
private fun loadAndCmp0MSB(value: PtExpression) {
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)
asmgen.out(" lda ${varname}_msb,y")
@@ -800,8 +915,8 @@ _jump jmp (${target.asmLabel})
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")
@@ -830,6 +945,7 @@ _jump jmp (${target.asmLabel})
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -878,8 +994,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")
@@ -909,6 +1027,7 @@ _jump jmp (${target.asmLabel})
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -974,6 +1093,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1023,8 +1143,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")
@@ -1054,6 +1176,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1136,7 +1259,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")
}
@@ -1157,7 +1282,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")
}
@@ -1197,6 +1324,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1249,6 +1377,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1303,6 +1432,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1362,6 +1492,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1424,6 +1555,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1482,6 +1614,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1532,8 +1665,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")
@@ -1596,13 +1731,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")
@@ -1648,13 +1783,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)
@@ -42,6 +44,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 -> {
@@ -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,40 +209,20 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section slabs_BSS")
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("prog8_slabs\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() {
asmgen.out("; expression temp vars\n .section BSS")
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")
}
private fun footer() {
asmgen.out(" .dsection STRUCTINSTANCES\n")
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
@@ -297,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 {
@@ -313,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\"")
}
}
@@ -351,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")
@@ -372,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
@@ -386,11 +366,93 @@ 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.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 = when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isFloat -> ".byte" // TODO check that float bytes are passed as an array parameter
else -> throw AssemblyError("weird dt")
}
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")
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(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") }
asmgen.out(" .send STRUCTINSTANCES\n")
}
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
return // subroutine gets inlined at call site.
@@ -440,7 +502,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
@@ -458,7 +520,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)")
@@ -494,14 +556,14 @@ internal class ProgramAndVarsGen(
sub.children.forEach { asmgen.translate(it) }
asmgen.out("; variables")
asmgen.out(" .section BSS")
asmgen.out(" .section BSS_NOCLEAR") // these extra vars are initialized before use
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
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.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -510,11 +572,11 @@ internal class ProgramAndVarsGen(
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
asmgen.out(" .send BSS")
asmgen.out(" .send BSS_NOCLEAR")
// 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)
@@ -569,12 +631,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("+")
@@ -638,22 +700,37 @@ internal class ProgramAndVarsGen(
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
asmgen.out(" .section BSS")
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
val (dirty, clean) = varsNoInit.partition { it.dirty }
fun generate(section: String, variables: List<StStaticVariable>) {
asmgen.out(" .section $section")
val (notAligned, aligned) = variables.partition { it.align == 0u }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base })
.forEach { uninitializedVariable2asm(it) }
asmgen.out(" .send $section")
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
if(clean.isNotEmpty()) {
// clean vars end up in BSS so they're at least cleared to 0 at startup
generate("BSS", clean)
}
if(dirty.isNotEmpty()) {
// Dirty vars actually are ALSO are put into BSS so they're cleared to 0 at program startup,
// but NOT at each entry of the subroutine they're declared in.
// This saves the STZ's instructions in the subroutine, while still having deterministic start state.
// So there is no actual difference here when compared to the way non-dirty variables are allocated.
generate("BSS", dirty)
}
asmgen.out(" .send BSS")
}
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,
@@ -700,23 +777,27 @@ internal class ProgramAndVarsGen(
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()}")
}
@@ -753,7 +834,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")
@@ -761,7 +842,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 -> {
@@ -863,7 +944,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())
@@ -928,7 +1009,7 @@ 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')
}
@@ -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)
@@ -46,6 +48,12 @@ internal class AnyExprAsmGen(
}
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, // wherever the pointer variable points to
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")
}
}
@@ -134,11 +148,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 +177,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 +223,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,22 +85,23 @@ 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)
}
}
}
@@ -205,7 +207,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 +226,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) {
@@ -301,7 +309,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 +328,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 +364,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 +374,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 +385,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 +447,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 +458,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 +512,24 @@ 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 */ }
}
}
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 +585,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 +899,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 +925,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 +987,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 +1007,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 +1034,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 +1140,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 +1212,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 +1385,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 +1556,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@@ -1921,18 +1943,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 +2278,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
@@ -2468,7 +2488,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.
when (operator) {
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
@@ -2890,7 +2910,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 +2925,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("""
File diff suppressed because it is too large. Load diff
+3 -1
View File
@@ -5,7 +5,9 @@ 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
+4
View File
@@ -58,6 +58,7 @@ class TestCodegen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -67,6 +68,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -76,6 +78,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -85,6 +88,7 @@ class TestCodegen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
+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,10 @@
package prog8.codegen.intermediate
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.*
@@ -26,9 +28,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"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)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
@@ -45,6 +50,8 @@ 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")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -493,8 +500,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 = "$StMemorySlabPrefix.prog8_memoryslab_$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 = labelname)
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -544,7 +559,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 +630,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 +666,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 +676,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 +697,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)
}
}
}
@@ -1,14 +1,8 @@
package prog8.codegen.intermediate
import prog8.code.StExtSub
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StSub
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.*
@@ -89,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")
@@ -164,48 +254,142 @@ 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) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null)
if(expr.identifier.type.isUnsignedWord) {
val indexWordReg = if(indexTr.dt==IRDataType.BYTE) {
val ixWord = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null)
ixWord
} else indexTr.resultReg
val resultRegister = codeGen.registers.next(vmDt)
if(identifier!!.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
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 = 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 {
@@ -219,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)
@@ -342,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)
@@ -373,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)
@@ -384,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)
@@ -441,9 +682,9 @@ 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.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
@@ -452,7 +693,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,7 +709,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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,7 +725,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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,7 +747,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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,7 +772,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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.FLOAT -> {
@@ -546,49 +790,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)
@@ -637,7 +893,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 {
@@ -768,6 +1024,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)
@@ -777,7 +1036,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}")
}
}
}
@@ -810,7 +1069,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())
@@ -1045,7 +1304,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)
@@ -1060,7 +1319,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)
@@ -1220,7 +1479,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) {
@@ -1235,11 +1495,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 {
@@ -1247,35 +1507,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
@@ -1297,7 +1558,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 {
@@ -1305,20 +1566,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)
}
}
@@ -1434,6 +1696,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
}
}
@@ -56,6 +56,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 +67,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 +105,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 +266,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 +336,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 +432,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 +446,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 +489,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 +502,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 +513,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 +525,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 +569,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 +655,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 +792,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 +810,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 +873,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 +1677,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 +1693,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 +1770,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 +1861,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 +1910,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 +1950,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 +1961,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)
@@ -1954,10 +1978,83 @@ class IRCodeGen(
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")
}
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)
}
}
@@ -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 OpcodesThatJump) {
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 {
@@ -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,17 @@ 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 = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convert(variable: StStaticVariable): IRStStaticVariable {
if('.' in variable.name) {
return IRStStaticVariable(variable.name,
@@ -52,7 +63,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align)
variable.align,
variable.dirty)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
@@ -61,14 +73,14 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
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))
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,
@@ -76,7 +88,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align
variable.align,
variable.dirty
)
}
}
@@ -92,7 +105,7 @@ private fun convert(variable: StMemVar): IRStMemVar {
)
} else {
val scopedName = try {
variable.scopedName
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
@@ -107,7 +120,7 @@ private fun convert(constant: StConstant): IRStConstant {
constant.name
} else {
try {
constant.scopedName
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
@@ -120,12 +133,17 @@ private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
else
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.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 IRStStructInstance(instance.name, instance.structName, values, instance.size)
}
*/
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?
+3 -1
View File
@@ -3,7 +3,9 @@ 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
+10
View File
@@ -52,6 +52,7 @@ class TestVmCodeGen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -61,6 +62,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -70,6 +72,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -79,6 +82,7 @@ class TestVmCodeGen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -167,6 +171,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -238,6 +243,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -305,6 +311,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -360,6 +367,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -431,6 +439,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@@ -498,6 +507,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
+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
}
@@ -332,15 +332,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 +394,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
@@ -323,7 +323,7 @@ internal class ConstantIdentifierReplacer(
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, 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)
@@ -18,8 +18,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 +33,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 +49,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 +57,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)
)
}
@@ -242,14 +242,16 @@ 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)
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
@@ -262,14 +264,16 @@ 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)
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
@@ -423,6 +427,31 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
if((arrayIndexedExpression.parent as? BinaryExpression)?.operator !=".") {
val 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))
}
}
}
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
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
// NOTE: only when the terms are not function calls!!!
if(expr.left is IFunctionCall || expr.right is IFunctionCall)
@@ -499,7 +528,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 +545,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 +584,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 +740,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 +868,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 +916,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 +932,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
}
@@ -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 {
+3 -6
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("application")
kotlin("jvm")
@@ -18,18 +16,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 +78,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>
+79 -11
View File
@@ -317,10 +317,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 +434,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
@@ -663,7 +680,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
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 +963,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
@@ -984,6 +1013,7 @@ cx16 {
&uword r14 = $1bfc
&uword r15 = $1bfe
; signed word versions
&word r0s = $1be0
&word r1s = $1be2
&word r2s = $1be4
@@ -1001,6 +1031,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 +1066,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 +1101,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
-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.
+61 -8
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
@@ -439,12 +439,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
@@ -685,7 +687,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
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 +970,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
@@ -1006,6 +1020,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@@ -1023,6 +1038,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 +1073,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 +1108,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
-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
+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
+19 -20
View File
@@ -89,18 +89,18 @@ sprites {
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
cx16.VERA_DATA1 = @(xpositions_ptr+num_sprites)
xpositions_ptr ++
}
tovera(xpositions_ptr)
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
cx16.VERA_DATA1 = @(ypositions_ptr+num_sprites)
ypositions_ptr ++
tovera(ypositions_ptr)
sub tovera(uword positions @R0) {
repeat num_sprites {
cx16.VERA_DATA0 = @(cx16.r0)
cx16.VERA_DATA1 = @(cx16.r0+num_sprites)
cx16.r0 ++
}
}
}
@@ -109,20 +109,19 @@ sprites {
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
xpositions_ptr ++
cx16.VERA_DATA1 = @(xpositions_ptr)
xpositions_ptr ++
}
tovera(xpositions_ptr)
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
ypositions_ptr ++
cx16.VERA_DATA1 = @(ypositions_ptr)
ypositions_ptr ++
tovera(ypositions_ptr)
sub tovera(uword positions @R0) {
repeat num_sprites {
cx16.VERA_DATA0 = @(cx16.r0)
cx16.r0 ++
cx16.VERA_DATA1 = @(cx16.r0)
cx16.r0 ++
}
}
}
+127 -44
View File
@@ -177,6 +177,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@@ -194,6 +195,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@@ -228,6 +230,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@@ -262,6 +265,42 @@ cx16 {
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
; VERA registers
const uword VERA_BASE = $9F20
@@ -474,7 +513,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
extsub $C000 = x16edit_default() clobbers(A,X,Y)
@@ -1168,8 +1207,39 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
sub joysticks_detect() -> ubyte {
; returns bits 0-4, set to 1 if that joystick is present.
; bit 0 = keyboard joystick, bit 1 - 4 = joypads 1 to 4
cx16.r0H = 255
for cx16.r0L in 4 downto 0 {
void cx16.joystick_get(cx16.r0L)
%asm {{
cpy #1 ; present?
}}
rol(cx16.r0H)
}
return ~cx16.r0H
}
; Commander X16 IRQ dispatcher routines
sub joysticks_getall(bool also_keyboard_js) -> uword {
; returns combined pressed buttons from all connected joysticks
; note: returns the 'normal' not inverted status bits for the buttons (1 = button pressed.)
cx16.r0H = 1
if also_keyboard_js
cx16.r0H = 0
cx16.r1 = $ffff
for cx16.r0L in cx16.r0H to 4 {
bool notpresent
cx16.r2, notpresent = cx16.joystick_get(cx16.r0L)
if not notpresent {
cx16.r1 &= cx16.r2
}
}
return ~cx16.r1
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
@@ -1216,6 +1286,7 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
@@ -1224,44 +1295,42 @@ _irq_dispatcher
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bit #2
stz _continue_with_system_handler
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
beq +
pha
jsr _line_handler
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
jsr _sprcol_handler
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
jsr _aflow_handler
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
pha
jsr _vsync_handler
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
tsb _continue_with_system_handler
pla
+ lsr a
lsr a
bcc +
pha
jsr _sprcol_handler
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
jsr _aflow_handler
tsb _continue_with_system_handler
+ jsr sys.restore_prog8_internals
lda _continue_with_system_handler
beq _no_sys_handler
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
_no_sys_handler
ply
plx
pla
@@ -1401,7 +1470,7 @@ sub search_x16edit() -> ubyte {
}}
}
sub set_program_args(uword args_ptr, ubyte args_size) {
sub set_program_args(str args_ptr, ubyte args_size) {
; -- Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
@@ -1413,7 +1482,7 @@ sub search_x16edit() -> ubyte {
rambank(sys.pop())
}
asmsub get_program_args(uword buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
asmsub get_program_args(^^ubyte buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
@@ -1485,12 +1554,14 @@ sys {
const ubyte target = 16 ; 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
@@ -1688,7 +1759,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
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
@@ -1997,6 +2068,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
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
-20
View File
@@ -17,26 +17,6 @@ const long VERA_TEXTMATRIX = $1b000
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)
}
+2
View File
@@ -119,6 +119,7 @@ verafx {
; Returns the 16 bits unsigned result of R0*R1 in AY.
; Note: only the lower 16 bits! (the upper 16 bits are not valid for unsigned word multiplications, only for signed)
; Verafx doesn't support unsigned values like this for full 32 bit result.
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda cx16.r0
sta P8ZP_SCRATCH_W1
@@ -136,6 +137,7 @@ verafx {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25
+2 -2
View File
@@ -6,7 +6,7 @@ cx16logo {
%option ignore_unused
sub logo_at(ubyte column, ubyte row) {
uword strptr
^^ubyte strptr
for strptr in logo_lines {
txt.plot(column, row)
txt.print(strptr)
@@ -15,7 +15,7 @@ cx16logo {
}
sub logo() {
uword strptr
^^ubyte strptr
for strptr in logo_lines {
txt.print(strptr)
txt.nl()
+25 -3
View File
@@ -3,7 +3,7 @@
;
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
; https://codebase64.net/doku.php?id=base:6502_6510_maths
; https://github.com/TobyLobster/multiply_test
; https://github.com/TobyLobster/sqrt_test
@@ -241,6 +241,28 @@ divmod_ub_asm .proc
rts
.pend
remainder_ub_asm .proc
; -- divide A by Y, returns remainder in A (unsigned)
; division by zero will result in just the original number.
; This routine specialcases 0,1,2 and otherwise is just a repeated subtraction.
cpy #0
beq _zero
cpy #1
bne +
lda #0
rts
+ cpy #2
bne +
and #1
rts
+ sty P8ZP_SCRATCH_REG
sec
- sbc P8ZP_SCRATCH_REG
bcs -
adc P8ZP_SCRATCH_REG
_zero rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2
@@ -331,7 +353,7 @@ _divisor .word ?
randword .proc
; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137. NOTE: uses self-modifying code so won't work in ROM (use randword_rom instead)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
@@ -355,7 +377,7 @@ b1=*+1
randword_rom .proc
; -- 16 bit pseudo random number generator into AY. Can run from ROM.
; NOTE: you have to set the initial seed using randseed_rom! (a good default seed = $00c2 $1137)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc _x1
clc
lda _x1
+23 -12
View File
@@ -380,7 +380,7 @@ _quadrant_region_to_direction:
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; https://www.codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{
@@ -547,7 +547,7 @@ log2_tab
}}
}
sub crc16(uword data, uword length) -> uword {
sub crc16(^^ubyte data, uword length) -> uword {
; calculates the CRC16 (XMODEM) checksum of the buffer.
; There are also "streaming" crc16_start/update/end routines below, that allow you to calculate crc16 for data that doesn't fit in a single memory block.
crc16_start()
@@ -602,7 +602,7 @@ log2_tab
return cx16.r15
}
sub crc32(uword data, uword length) {
sub crc32(^^ubyte data, uword length) {
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
@@ -697,25 +697,36 @@ log2_tab
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}
+60 -7
View File
@@ -98,12 +98,14 @@ sys {
const ubyte target = 32 ; 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 = 0 ; undefined, no floats supported
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
@@ -161,7 +163,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
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
@@ -481,6 +483,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
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
@@ -519,6 +533,7 @@ cx16 {
&uword r14 = $7ffc
&uword r15 = $7ffe
; signed word versions
&word r0s = $7fe0
&word r1s = $7fe2
&word r2s = $7fe4
@@ -536,6 +551,7 @@ cx16 {
&word r14s = $7ffc
&word r15s = $7ffe
; ubyte versions (low and high bytes)
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
&ubyte r2L = $7fe4
@@ -570,6 +586,7 @@ cx16 {
&ubyte r14H = $7ffd
&ubyte r15H = $7fff
; signed byte versions (low and high bytes)
&byte r0sL = $7fe0
&byte r1sL = $7fe2
&byte r2sL = $7fe4
@@ -604,6 +621,42 @@ cx16 {
&byte r14sH = $7ffd
&byte r15sH = $7fff
; boolean versions
&bool r0bL = $7fe0
&bool r1bL = $7fe2
&bool r2bL = $7fe4
&bool r3bL = $7fe6
&bool r4bL = $7fe8
&bool r5bL = $7fea
&bool r6bL = $7fec
&bool r7bL = $7fee
&bool r8bL = $7ff0
&bool r9bL = $7ff2
&bool r10bL = $7ff4
&bool r11bL = $7ff6
&bool r12bL = $7ff8
&bool r13bL = $7ffa
&bool r14bL = $7ffc
&bool r15bL = $7ffe
&bool r0bH = $7fe1
&bool r1bH = $7fe3
&bool r2bH = $7fe5
&bool r3bH = $7fe7
&bool r4bH = $7fe9
&bool r5bH = $7feb
&bool r6bH = $7fed
&bool r7bH = $7fef
&bool r8bH = $7ff1
&bool r9bH = $7ff3
&bool r10bH = $7ff5
&bool r11bH = $7ff7
&bool r12bH = $7ff9
&bool r13bH = $7ffb
&bool r14bH = $7ffd
&bool r15bH = $7fff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
+10 -34
View File
@@ -15,26 +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() {
chrout(7)
}
@@ -225,20 +205,16 @@ sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor_ignored) {
asmsub plot (ubyte col @ Y, ubyte row @ X) {
%asm {{
jsr home
cpy #0
beq +
- lda #29
jsr chrout
dey
bne -
+ cpx #0
beq +
- lda #17
jsr chrout
dex
bne -
+ rts
stx $d8
txa
asl a
tax
lda setchr._screenrows,x
sta $c4
lda setchr._screenrows+1,x
sta $c5
sty $c6
rts
}}
}
+10 -1
View File
@@ -401,8 +401,17 @@ _loop_hi ldy _index_first
.pend
func_peek .proc
; -- read the byte value on the address in AY, into A
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
rts
.pend
func_peekw .proc
; -- read the word value on the address in AY
; -- read the word value on the address in AY, into AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
+21 -21
View File
@@ -101,7 +101,7 @@ io_error:
}
sub diskname() -> uword {
sub diskname() -> str {
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
@@ -145,7 +145,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
@@ -154,7 +154,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.
@@ -163,7 +163,7 @@ io_error:
; Note that no list of pointers of some form is returned, the names are just squashed together.
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
; and registering when the next one starts after the 0-byte separator.
uword buffer_start = filenames_buffer
uword buffer_start = filenames_buffer
ubyte files_found = 0
if lf_start_list(pattern_ptr) {
while lf_next_entry() {
@@ -187,7 +187,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, "$")
@@ -217,7 +217,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!
@@ -225,7 +225,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!
@@ -244,7 +244,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()
@@ -311,7 +311,7 @@ close_end:
; ----- iterative file loader functions (uses the input io channel) -----
sub f_open(uword filenameptr) -> bool {
sub f_open(str filenameptr) -> bool {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
@@ -413,8 +413,8 @@ close_end:
return total_read
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
asmsub f_readline(^^ubyte bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read a text line 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).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
@@ -461,7 +461,7 @@ _end jsr cbm.READST
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
sub f_open_w(str filenameptr) -> bool {
; -- open a file for iterative writing with f_write
; WARNING: returns true if the open command was received by the device,
; but this can still mean the file wasn't successfully opened for writing!
@@ -516,7 +516,7 @@ _end jsr cbm.READST
; ---- 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
@@ -527,7 +527,7 @@ _end jsr cbm.READST
; 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
@@ -586,7 +586,7 @@ io_error:
return 255
}
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
sub save(str filenameptr, uword start_address, uword savesize) -> bool {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = start_address + savesize
@@ -617,7 +617,7 @@ io_error:
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the end load address+1 if successful or 0 if a load error occurred.
sub load(uword filenameptr, uword address_override) -> uword {
sub load(str filenameptr, uword address_override) -> uword {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
@@ -641,7 +641,7 @@ io_error:
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(uword filenameptr, uword start_address) -> uword {
sub load_raw(str filenameptr, uword start_address) -> uword {
; read the 2 header bytes separately to skip them
if not f_open(filenameptr)
return 0
@@ -654,12 +654,12 @@ io_error:
}
; 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 load(libnameptr, libaddress)
}
sub delete(uword filenameptr) {
sub delete(str filenameptr) {
; -- delete a file on the drive
list_filename[0] = 's'
list_filename[1] = ':'
@@ -671,7 +671,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] = ':'
@@ -696,7 +696,7 @@ io_error:
return false
}
sub send_command(uword commandptr) {
sub send_command(str commandptr) {
; -- send a dos command to the drive
cbm.SETNAM(strings.length(commandptr), commandptr)
cbm.SETLFS(15, drivenumber, 15)
@@ -3,6 +3,35 @@ txt {
%option merge, no_symbol_prefixing, ignore_unused
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub rvs_on() {
txt.chrout(18)
}
sub rvs_off() {
txt.chrout(146)
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print zero terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
@@ -136,7 +165,7 @@ _allzero lda #'0'
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
asmsub input_chars (^^ubyte buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
Loaded 100 of 286 files, more files were not shown because too many files have changed in this diff. Show more