Compare commits

...
231 Commits
Author SHA1 Message Date
Irmen de Jong 1f5706bbeb version 10.5.1 2024-11-20 22:54:26 +01:00
Irmen de Jong 25c9b2fea4 remove an archaic machine code monitor bank setting at program exit
what did it even do?  in any case, $2d is just a user zero page location it should no longer be associated with the monitor nowadays.
2024-11-20 19:28:31 +01:00
Irmen de Jong 154f9b300f fix crash: byte c = if a < b -1 else 1 "both values should be the same type" 2024-11-19 23:46:25 +01:00
Irmen de Jong d78ce77536 improve vm error message when referencing a block name 2024-11-19 20:57:58 +01:00
Irmen de Jong b4fb43bc80 fix the if not check in ir codegen 2024-11-18 22:33:47 +01:00
Irmen de Jong e0e01f794e fix dt compiler crash 2024-11-17 17:39:36 +01:00
Irmen de Jong 08865dbb4e todo 2024-11-16 02:10:57 +01:00
Irmen de Jong b9ad7e0e55 forgot to mention floats 2024-11-15 23:37:08 +01:00
Irmen de Jong 07158a6f1a improve manual about subroutine call convention 2024-11-15 22:59:47 +01:00
Irmen de Jong 957c42bc1d tweak 2024-11-15 02:52:21 +01:00
Irmen de Jong f784da2da6 fix asm optimization regression caused by wrong label prefix comparison 2024-11-14 21:24:46 +01:00
Irmen de Jong c080fbe59a target machine config tweak and fix possible compiler crash on wrong type name 2024-11-13 21:04:46 +01:00
Irmen de Jong d70b8303b1 added sprites.reset() to remove sprites from the screen 2024-11-13 20:26:04 +01:00
Irmen de Jong 1d38c3582a progstart() added to complement progend() 2024-11-13 19:29:50 +01:00
Irmen de Jong 9438e996d7 Fixed math.mul16_last_upper().
Added math.lerpw() a LERP routine for words (to complement the existing math.lerp() for bytes)
Described the LERP routines in the library chapter in the docs.
2024-11-12 18:31:24 +01:00
Irmen de Jong 3b4a5e27f7 renamed gfx_hires4 module to just gfx_hires
to be consistent with gfx_lores
2024-11-12 17:48:35 +01:00
Irmen de Jong 648d9fc269 todo 2024-11-12 00:57:10 +01:00
Irmen de Jong 54fccec7d7 now also support using defer inside if statements 2024-11-12 00:11:19 +01:00
Irmen de Jong 4f9693055e fix compiler crash when extsub has both FAC1 and FAC2 float parameters 2024-11-11 20:48:25 +01:00
Irmen de Jong 555c50ee10 scripts/cx16_images : added an option to keep only the first palette entry fixed to a given color (such as, black.) 2024-11-11 19:27:06 +01:00
Irmen de Jong bf98ceca2c make repeat support 65536 iterations 2024-11-11 01:58:27 +01:00
Irmen de Jong 573cecb087 make memtop adjust automatically when you use %address larger than the default memtop setting. 2024-11-10 23:44:10 +01:00
Irmen de Jong 1b528491c2 make %memtop exclusive i.e. the first address NOT to use (like kernal MEMTOP) 2024-11-10 23:35:25 +01:00
Irmen de Jong 4bdabe1961 move shared cbm diskio to its own file (c64/c128) so that pet/atari/neo targets give better error message when trying to import non existing diskio module there
sorted the command line options alphabetically
2024-11-10 16:38:49 +01:00
Irmen de Jong a3fa527378 move shared cbm diskio to its own file (c64/c128) so that pet/atari/neo targets give better error message when trying to import non existing diskio module there 2024-11-10 15:35:36 +01:00
Irmen de Jong 84f5ffa426 fix generated labels prefix and filtering in the vice symbol dump file 2024-11-10 15:34:35 +01:00
Irmen de Jong 25d2b42283 textelite now with sysinit, so it runs on the c128 as well (needs banking) 2024-11-09 16:03:16 +01:00
Irmen de Jong 300d1a871c c128 banks out basic, added banks() and getbanks() 2024-11-09 15:44:08 +01:00
Irmen de Jong 2fcb83a39f version 10.5 2024-11-09 14:04:04 +01:00
Irmen de Jong 3ba1d00a7c add unit test for @dirty variables 2024-11-09 13:31:54 +01:00
Irmen de Jong 64164c1c72 changed @initonce to @dirty and meaning is now: not initialized at all. 2024-11-08 22:05:31 +01:00
Irmen de Jong 3ee6058524 todo 2024-11-08 19:57:38 +01:00
Irmen de Jong 93a0a41e73 Merge branch 'initonce-var-tag'
# Conflicts:
#	examples/test.p8
2024-11-08 19:32:30 +01:00
Irmen de Jong e7ab7b6d7a neo skeletons added in docs 2024-11-08 19:19:11 +01:00
Irmen de Jong 7d4dc3c063 update 2024-11-08 19:04:51 +01:00
Irmen de Jong a50400b7d1 initial neo6502 target 2024-11-08 19:04:49 +01:00
Irmen de Jong f89f1a84d0 @initonce variable tag to skip variable reinitialization 2024-11-08 19:03:48 +01:00
Irmen de Jong 688dce6145 floats: added AYINT2 as a safe wrapper for AYINT. Internal float to word cast now also uses that. 2024-11-08 18:52:48 +01:00
Irmen de Jong b88f550c5b todo 2024-11-07 00:48:13 +01:00
Irmen de Jong 9864abd393 romsub keyword is now extsub 2024-11-06 22:14:53 +01:00
Irmen de Jong c702c4a6df internal rename of romsub to extsub 2024-11-06 21:42:16 +01:00
Irmen de Jong 77e376f6bf romsub @bank now also accepts a variable so the bank can be dynamic 2024-11-06 00:02:36 +01:00
Irmen de Jong 491e5dbcfb move the program startup and cleanup machinery to the front of the program to keep it in system ram 2024-11-05 22:12:25 +01:00
Irmen de Jong a5c7393561 tweaking program startup and cleanup stuff 2024-11-05 21:12:27 +01:00
Irmen de Jong 7fd3e9bb7d also provide a X16-style JSRFAR implementation for the C64. Enable callfar() and callfar2() on the C64 and C128. 2024-11-05 19:26:58 +01:00
Irmen de Jong 459e9f8f3b jsrfar stuff 2024-11-05 01:06:06 +01:00
Irmen de Jong 5b1143bcb3 C64: add support for calling romsub with bank ('jsrfar') 2024-11-04 23:26:21 +01:00
Irmen de Jong fddd390d31 on the C64, if not using floats, disable basic ROM in startup to gain another 8Kb of RAM
MEMTOP is adjusted to $d000. This gives us 50 Kb of contiguous program RAM space. ($0801-$CFFF)
2024-11-04 22:11:44 +01:00
Irmen de Jong e514eeba17 added c64.banks() and c64.getbanks() and c64 banking example 2024-11-04 20:14:30 +01:00
Irmen de Jong c11a52b278 added cx16 banking example 2024-11-03 21:52:04 +01:00
Irmen de Jong 85e87dfe2e consolidate @rombank and @rambank into just @bank 2024-11-03 21:15:11 +01:00
Irmen de Jong cb47e2c149 documented the romsub bank additions 2024-11-03 20:39:44 +01:00
Irmen de Jong 0fc9aa6b2d cx16: romsubs of the audio routines now have the rom bank tag.
cx16: removed 'audio' module again, no longer needed to have these stubs
2024-11-03 18:35:10 +01:00
Irmen de Jong 155896c4c7 added @rombank and @rambank bank number tags on romsubs
on cx16 and c128 targets the compiler then automatically inserts a CALLFAR instead of a regular JSR to automatically do the bank switching.
2024-11-03 18:19:31 +01:00
Irmen de Jong 178e60bba0 fix ast source gen for romsub 2024-11-03 15:04:53 +01:00
Irmen de Jong 9f84aa5fb2 fix double %option merge problem where it deleted all of the blocks 2024-11-03 13:36:14 +01:00
Irmen de Jong 66fc109ce5 correct program name in help 2024-11-02 22:16:57 +01:00
Irmen de Jong a231872821 tip for using aliases for the virtual registers r0-r15 2024-11-02 22:09:20 +01:00
Irmen de Jong 7cfb33a448 tweak & fix if expression with word condition 2024-11-02 22:01:57 +01:00
Irmen de Jong 3b798097b9 added memtop to machine definition and asm source code check
added %memtop directive
2024-11-02 00:59:07 +01:00
Irmen de Jong 6fb05bdefc replaced deprecated cx16 ZSOUND example by new ZSMKIT examples 2024-11-01 23:17:23 +01:00
Irmen de Jong 64ea72ed4d tweak plot 2024-11-01 21:56:27 +01:00
Irmen de Jong 89425088ce taking address of a split word array is no longer a fatal error but a warning and the array is turned back into a normal word array. 2024-11-01 20:18:31 +01:00
Irmen de Jong 925b9d845d fix split array possible compiler loop (due to wrong datatype replacement) 2024-11-01 19:18:03 +01:00
Irmen de Jong ad074076c2 remove last references to gfx2 module 2024-11-01 18:41:36 +01:00
Irmen de Jong a2194c43a6 fix benchmark 2024-11-01 03:50:13 +01:00
Irmen de Jong 4b23b1dc86 don't always import math automatically anymore 2024-11-01 03:39:52 +01:00
Irmen de Jong 9005c7994a added Linear Interpolation (LERP) functions: math.lerp(), floats.lerp(), floats.lerp_fast() 2024-11-01 02:05:48 +01:00
Irmen de Jong 4a47e15b1c fix IR if expression sometimes lacking a cmpi after calculation of the condition value
VM/IR: add a returni immediate value return instruction to replace certain returnr's
2024-11-01 01:04:16 +01:00
Irmen de Jong 09cbdf410a added diskio.exists(), made f_close_w() idempotent like f_close() already was 2024-10-31 21:25:22 +01:00
Irmen de Jong df6a43c7f0 gfx_lores now has drawmode_eor() (used by Paint, for example) 2024-10-31 01:28:29 +01:00
Irmen de Jong 4ce130dc8b split up cx16.gfx2 module into gfx_lores and gfx_hires4 modules 2024-10-30 22:21:07 +01:00
Irmen de Jong 94d76aa82c cx16.vaddr(), vaddr_clone(), vaddr_autoincr(), vaddr_autodecr() now all reset vera's ADDRSEL back to 0 even if the configured port was 1 2024-10-30 21:40:58 +01:00
Irmen de Jong 73609636c5 gfx_lores.set_screen_mode() is now gfx_lores.graphics_mode()
adding all missing routines from gfx2 to gfx_lores
2024-10-30 21:39:37 +01:00
Irmen de Jong 66b06d6c40 added gfx2.safe_vertical_line, gfx2.safe_rect, gfx2.safe_fillrect for completeness 2024-10-30 19:03:40 +01:00
Irmen de Jong eeeb8d81f4 merge now also allows monkeypatching if signature is 100% identical 2024-10-30 01:15:56 +01:00
Irmen de Jong 6f727aff88 fix beanshell compile with jdk11 2024-10-29 23:42:37 +01:00
Irmen de Jong 518e5a30c2 slight parser rule tweak 2024-10-29 23:18:17 +01:00
Irmen de Jong bbba4b3d60 new block merge semantics and implementation 2024-10-29 22:57:54 +01:00
Irmen de Jong 967adb9a87 Merge branch 'beanshell' 2024-10-29 20:55:09 +01:00
Irmen de Jong 040a6c62de added a beanshell interpreter experiment 2024-10-29 20:52:41 +01:00
Irmen de Jong 483d193ced vm: implemented reading/writing files in diskio 2024-10-29 02:34:53 +01:00
Irmen de Jong 62458216c9 first skeleton of LSP language server 2024-10-28 21:42:20 +01:00
Irmen de Jong 76b05cb5fd fix chained aliasing 2024-10-28 18:35:23 +01:00
Irmen de Jong 570b574b93 added sys.memcmp 2024-10-28 00:41:26 +01:00
Irmen de Jong a82f211f9a added alias statement 2024-10-28 00:36:10 +01:00
Irmen de Jong 504c80cddf fix parser rule for identifiers (void is a keyword, not an identifier) 2024-10-27 15:57:27 +01:00
Irmen de Jong 4b4af9b527 no longer silently add RTS to asmsubs that don't have one 2024-10-27 13:49:00 +01:00
Irmen de Jong 28b383f888 docs and syntax for @alignxxx and %align 2024-10-27 00:47:52 +02:00
Irmen de Jong 40ce7725a1 cleanup c64 sprite examples 2024-10-26 21:36:11 +02:00
Irmen de Jong 1f2d46628e remove %option align_xxx (block level alignment, as we now have better alternatives) 2024-10-26 21:18:34 +02:00
Irmen de Jong c9535049c8 %align directive and @align64 2024-10-26 20:58:35 +02:00
Irmen de Jong 9317cf8a35 sorting aligned vars to shrink prg size 2024-10-26 18:33:51 +02:00
Irmen de Jong 1cd754f05d adding @alignword/page on individual variables 2024-10-26 17:00:38 +02:00
Irmen de Jong 97b8cb748d more ifexpression codegen tweaks 2024-10-25 22:52:26 +02:00
Irmen de Jong 84d9040b57 make BIT test also work on signed byte variables. Fixed an address-of optimization error. 2024-10-23 22:34:18 +02:00
Irmen de Jong fdd18c615c more ifexpresssion codegen tweaks 2024-10-23 21:04:55 +02:00
Irmen de Jong c14f6cfc2b more optimal if expression code 2024-10-22 23:49:24 +02:00
Irmen de Jong 326eab3dd1 unit test for defer, describe defer and if expression in docs 2024-10-22 22:19:49 +02:00
Irmen de Jong 6da1f7eb4c don't remove essential subroutines even though they seem unused 2024-10-22 21:17:02 +02:00
Irmen de Jong 1e82483152 ast printer correctly prints ifexpression 2024-10-22 21:14:55 +02:00
Irmen de Jong 6e2fd41a8b ast printer correctly prints unroll and continue 2024-10-22 21:14:26 +02:00
Irmen de Jong 9927af1095 about var inits 2024-10-22 01:08:42 +02:00
Irmen de Jong 7585b6ef6f fix issues with calling the defer handler 2024-10-21 19:49:38 +02:00
Irmen de Jong a6159702da defers are now only registered/called when flow of control actually reached the defer statement
a defer statement sets its corresponding bit in a bitmask that is shifted in the defer handler routine to see what defer blocks to call.
2024-10-21 00:55:51 +02:00
Irmen de Jong 0247fb0d84 some ast2 var tweaks 2024-10-21 00:20:54 +02:00
Irmen de Jong 6de760885f fix defer push/pop typecasting issues 2024-10-19 21:45:49 +02:00
Irmen de Jong 9851d14fb9 added if expression: ubyte a = if b>0 44 else 55
it doesn't generate the best code yet, like regular ifs do.
2024-10-19 15:34:04 +02:00
Irmen de Jong d5fc69d3e4 fix instruction index error in optimizer 2024-10-19 14:45:53 +02:00
Irmen de Jong a40d120f2a more defer sanity checks 2024-10-18 22:32:49 +02:00
Irmen de Jong fcdd9414d9 fix defer interfering with return value, fix prefix expression error when operand is functioncall that doesn't return a value. 2024-10-18 21:43:09 +02:00
Irmen de Jong 272a1001a8 fix bad optimization of floats.pop/push call 2024-10-18 21:04:18 +02:00
Irmen de Jong 2a52241f1c defer is now done *after* calculating a return value 2024-10-18 20:56:27 +02:00
Irmen de Jong d8f1822c12 fixes 2024-10-18 20:32:46 +02:00
Irmen de Jong ce7d094adb Zig-like "defer" to clean up stuff when leaving the scope of the current routine. 2024-10-18 01:30:20 +02:00
Irmen de Jong a0cf1889a3 omit more redundant 0-initializations ("stz's") 2024-10-17 22:51:39 +02:00
Irmen de Jong 38ef394e15 IR codegen: global vars with numeric initialization value are now also put into the VARIABLESWITHINIT section rather than requiring explicit code instructions to initialize them in INITGLOBALS.
Note that something similar, such as putting those variables inline in the program initialized with their value and all, cannot be done for the 6502 codegen: the program needs a mechanism to reset ALL variables when it runs a second time.
2024-10-16 22:15:51 +02:00
Irmen de Jong abbf7c7cb0 compiler name change: prog8c (was p8compile)
fat jar file also changed:  prog8c-X.Y-all.jar    (was: prog8compiler-X.Y-all.jar)
2024-10-16 18:36:19 +02:00
Irmen de Jong ca5f7ae32f global (block-level) variables that get initialized with an array index expression now get a constant value as well if possible. This reduces the number of instructions in the init globals code block 2024-10-16 02:14:19 +02:00
Irmen de Jong cbc4b75e50 IR now contains "bool" as a type instead of already erasing it into "ubyte". (boolean literals still are simply just 1 and 0 values) 2024-10-16 01:03:35 +02:00
Irmen de Jong 65ddcf91d0 remove unused syscalls 2024-10-15 18:24:55 +02:00
Irmen de Jong 5280e1b449 err msgs 2024-10-13 21:33:13 +02:00
Irmen de Jong b6ffb81909 Merge branch 'next-version' 2024-10-13 21:20:10 +02:00
Irmen de Jong e9edffa9f0 remove support for array-to-array assignments (other than initialization of variable declaration)
Just use an explicit sys.memcopy(src, dest, sizeof(dest))  or assign array members individually.
2024-10-13 20:02:43 +02:00
Irmen de Jong 0dd1c17ff4 avoid possible crash 2024-10-13 17:51:14 +02:00
Irmen de Jong aef211e5f3 stricter array literal element type handling (number,bool,address-of).
More consistent implicit address-of handling if array literals contain by-ref identifiers (such as subroutine names)
2024-10-13 17:46:41 +02:00
Irmen de Jong 66829203d8 New [x]*42 syntax to create array literals with repeated values (like "abc"*10 already exists for strings)
Should be used in place of array initializer expressions that contain only a single numeric value to initialize the whole array with. That isn't supported anymore.
2024-10-13 05:16:08 +02:00
Irmen de Jong 7a0eaf3148 Remove array initialization by single value.
New compiler and kotlin version.
2024-10-13 04:31:56 +02:00
Irmen de Jong fa5479ee5f fix ast printing of arrays with duplicate elements 2024-10-13 04:30:46 +02:00
Irmen de Jong 03412cacba added examples/cx16/balloonflight.p8 2024-10-13 00:51:07 +02:00
Irmen de Jong 01a38a0b11 Merge branch 'monogfx_fill_optimization' 2024-10-12 17:29:38 +02:00
Irmen de Jong f43c14bd78 doc 2024-10-12 17:29:28 +02:00
Irmen de Jong fb23452383 optimize monogfx.fill() 2024-10-12 17:18:00 +02:00
Irmen de Jong ab7dde1450 todo 2024-10-12 13:04:19 +02:00
Irmen de Jong 8d9bc2f5ff fixing all sorts of things about assigning arrays to arrays 2024-10-12 12:33:46 +02:00
Irmen de Jong 7651ccc84e fix a type error 2024-10-11 00:50:05 +02:00
Irmen de Jong 1a6b95b388 house cleaning 2024-10-10 20:46:18 +02:00
Irmen de Jong 78ec1e7512 version 2024-10-09 22:21:04 +02:00
Irmen de Jong 7e38d26c33 added several color fade functions to the palette module (cx16) 2024-10-09 21:48:04 +02:00
Irmen de Jong ed09dd4e9e improve automatic type conversions for return values, fixes #155 2024-10-09 20:04:05 +02:00
Irmen de Jong 5731b79554 don't allow problematic string and array assignments anymore, improve error messages.
In certain cases you will need to use string.copy() explicitly to overwrite strings with new strings.
2024-10-09 00:51:05 +02:00
Irmen de Jong eaa22a9d13 added callfar2() builtin function that allows to set A,X,Y and Carry arguments. 2024-10-08 21:36:04 +02:00
Irmen de Jong b2bdfe8482 fix ir rndseed() 2024-10-08 20:40:00 +02:00
Irmen de Jong fea531be9a add sys.sizeof_bool, _ubyte, _uword constants 2024-10-07 20:45:13 +02:00
Irmen de Jong 7c69d38588 scan all asmsubs to see if another subroutine is referenced. Fixes #153 2024-10-07 20:39:49 +02:00
Irmen de Jong a088ee56b0 function inlining can no longer get into an infinite loop. Fixes #154 2024-10-07 19:58:04 +02:00
Irmen de Jong ae669af904 add sys.sizeof_byte, _word, _float constants
because Antlr doesn't allow the grammar to contain a sizeof(typename) rule to override the sizeof(identifier) rule
2024-10-07 19:17:37 +02:00
Irmen de Jong d1ddf05e38 check that block address leaves room for program startup logic 2024-10-03 22:30:06 +02:00
Irmen de Jong 51279a98b3 attempt to fix forloop range datatype issues 2024-10-03 21:12:31 +02:00
Irmen de Jong bf33a4f82d small refactor to prepare for better range dt adjustment 2024-10-02 23:28:33 +02:00
Irmen de Jong fff0d741c3 improved parsing of "not in" operator, and [] array signature (allow space) 2024-10-02 19:06:20 +02:00
Irmen de Jong e83d0ee820 fix crash in msb() when assigning to word again. Fix wrong register in lsb() and msb() in certain situations. 2024-10-02 02:40:16 +02:00
Irmen de Jong 09f3eecf56 changed cx16/rotating-stars example to starszoom instead. 2024-10-02 01:36:54 +02:00
Irmen de Jong 2bd4326ff6 added cx16/rotating-stars example 2024-10-01 23:43:50 +02:00
Irmen de Jong c13168b60c various improvements:
fix verafx.available().
added gfx_lores.plot().
faster gfx_lores.clear_screen().
added a new Sublime Text 4 syntax highlighting file.
2024-10-01 22:18:03 +02:00
Irmen de Jong ea3871d0c4 comment about builtin function call ast node type 2024-10-01 02:14:31 +02:00
markjreed 70a2b11271 New example program: draw a fractal tree (#152)
* demo of prog8 recursion to draw a fractal tree

* feat: comments

* fix: comment formatting

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

* fix: remove duplicate definition

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

No files matched your search

+5 -2
View File
@@ -1,8 +1,12 @@
.idea/workspace.xml
.idea/discord.xml
.idea/developer-tools.xml
.idea/usage.statistics.xml
.idea/shelf/
build/
dist/
output/
out/
.*cache/
*.directory
*.prg
@@ -11,7 +15,6 @@ output/
*.vm.txt
*.vice-mon-list
docs/build
out/
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
@@ -22,6 +25,7 @@ compiler/src/prog8/buildversion/*
.eggs/
/MANIFEST
.tox/
.kotlin/
__pycache__/
parser.out
parsetab.py
@@ -30,7 +34,6 @@ parsetab.py
compiler/lib/
.gradle
/prog8compiler.jar
sd*.img
*.d64
+1 -1
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.24" />
<option name="version" value="2.0.21" />
</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.0.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.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.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-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.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-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.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-sources.jar!/" />
</SOURCES>
</library>
</component>
+13
View File
@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.23.1/org.eclipse.lsp4j-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.23.1/org.eclipse.lsp4j.jsonrpc-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="libraryTable">
<library name="io.kotest.framework.datatest" type="repository">
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+10 -2
View File
@@ -6,7 +6,15 @@
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+3
View File
@@ -2,6 +2,8 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" />
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
@@ -14,6 +16,7 @@
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
+2 -2
View File
@@ -4,9 +4,9 @@
all:
gradle installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
test:
gradle build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile"
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
+11 -10
View File
@@ -29,6 +29,8 @@ How to get it/build it
- Or, if you want/need a bleeding edge development version, you can:
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
Note that if you are not using *gradle* to build it, you might have to perform some manual
tasks once to make it compile fully. These are explained in the linked instructions.
- Alternatively, you can also install the compiler as a package on some linux distros:
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
@@ -52,27 +54,26 @@ What does Prog8 provide?
------------------------
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code
- programs run very fast because it's compiled to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- floating point math is supported on certain targets
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- no need for forward declarations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
- 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether
*Rapid edit-compile-run-debug cycle:*
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<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="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
+91
View File
@@ -0,0 +1,91 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
id("application")
}
val serverMainClassName = "prog8lsp.MainKt"
val applicationName = "prog8-beanshell"
val javaVersion: String by project
application {
mainClass.set(serverMainClassName)
description = "Code completions, diagnostics and more for Prog8"
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
applicationDistribution.into("bin") {
filePermissions {
user {
read=true
execute=true
write=true
}
other.execute = true
group.execute = true
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar"))
}
configurations.forEach { config ->
config.resolutionStrategy {
preferProjectModules()
}
}
sourceSets.main {
java.srcDir("src")
resources.srcDir("resources")
}
sourceSets.test {
java.srcDir("src")
resources.srcDir("resources")
}
tasks.startScripts {
applicationName = "prog8-beanshell"
}
tasks.register<Exec>("fixFilePermissions") {
// When running on macOS or Linux the start script
// needs executable permissions to run.
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell")
}
tasks.withType<Test>() {
testLogging {
events("failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}
tasks.installDist {
finalizedBy("fixFilePermissions")
}
tasks.build {
finalizedBy("installDist")
}
java {
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_11
}
kotlin {
compilerOptions.jvmTarget = JvmTarget.JVM_11
// jvmToolchain {
// languageVersion = JavaLanguageVersion.of(javaVersion.toInt())
// }
}
Binary file not shown.
@@ -0,0 +1,48 @@
package prog8beanshell
import java.io.FilterReader
import java.io.Reader
class CommandLineReader(val input: Reader): FilterReader(input) {
private val normal = 0
private val lastCharNL = 1
private val sentSemi = 2
private var state = lastCharNL
override fun read(): Int {
if (state == sentSemi) {
this.state = lastCharNL
return 10
} else {
var b = input.read()
while(b==13) b = input.read()
if (b == 10) {
if (this.state == lastCharNL) {
b = 59
this.state = sentSemi
} else {
this.state = lastCharNL
}
} else {
this.state = normal
}
return b
}
}
override fun read(buff: CharArray, off: Int, len: Int): Int {
val b = read()
if (b == -1) {
return -1
} else {
buff[off] = b.toChar()
return 1
}
}
}
+23
View File
@@ -0,0 +1,23 @@
package prog8beanshell
import bsh.FileReader
import bsh.Interpreter
class BeanshellInterpreter {
fun run(symbols: Map<String, Any>) {
val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true)
interpreter.setExitOnEOF(false)
symbols.forEach { (name, value) -> interpreter.set(name, value) }
interpreter.run()
}
}
fun main(args: Array<String>) {
val i = BeanshellInterpreter()
i.run(mapOf(
"env" to System.getenv(),
"args" to args
))
}
+10
View File
@@ -0,0 +1,10 @@
.PHONY: clean run
run:
prog8c -target cx16 benchmark.p8
x16emu -run -prg benchmark.prg -warp
clean:
rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z
+109
View File
@@ -0,0 +1,109 @@
%import textio
%import math
rotate3d {
const ubyte WIDTH = 40
const ubyte HEIGHT = 30
sub benchmark(uword max_time) -> uword {
uword anglex
uword angley
uword anglez
uword frames
txt.nl()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
matrix_math.rotate_vertices(msb(anglex), msb(angley), msb(anglez))
draw_edges() ; doesn't really draw anything in the benchmark, but does do the screen calculations
anglex+=500
angley+=215
anglez+=453
frames++
}
return frames
}
sub draw_edges() {
; plot the points of the 3d cube
; first the points on the back, then the points on the front (painter algorithm)
ubyte @zp i
word @zp rz
word @zp persp
byte @shared sx
byte @shared sy
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz >= 10 {
persp = 600 + rz/64
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
}
}
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz < 10 {
persp = 600 + rz/64
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
}
}
txt.chrout('.')
}
}
matrix_math {
; vertices
word[] @split xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
word[] @split ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
word[] @split zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
; storage for rotated coordinates
word[len(xcoor)] @split rotatedx
word[len(ycoor)] @split rotatedy
word[len(zcoor)] @split rotatedz
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
; rotate around origin (0,0,0)
; set up the 3d rotation matrix values
word wcosa = math.cos8(ax)
word wsina = math.sin8(ax)
word wcosb = math.cos8(ay)
word wsinb = math.sin8(ay)
word wcosc = math.cos8(az)
word wsinc = math.sin8(az)
word wcosa_sinb = wcosa*wsinb / 128
word wsina_sinb = wsina*wsinb / 128
word Axx = wcosa*wcosb / 128
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
word Ayx = wsina*wcosb / 128
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
word Azx = -wsinb
word Azy = wcosb*wsinc / 128
word Azz = wcosb*wcosc / 128
ubyte @zp i
for i in 0 to len(xcoor)-1 {
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
}
}
}
+89
View File
@@ -0,0 +1,89 @@
adpcm {
sub decode_benchmark(uword max_time) -> uword {
uword num_blocks
txt.nl()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
adpcm.init(0,0)
uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input
repeat 252/2 {
unroll 2 {
ubyte @zp nibble = @(nibbles_ptr)
adpcm.decode_nibble(nibble & 15) ; first word (note: upper nibble needs to be zero!)
adpcm.decode_nibble(nibble>>4) ; second word (note: upper nibble is zero, after the shifts.)
nibbles_ptr++
}
}
num_blocks++
txt.chrout('.')
}
return num_blocks
}
; IMA ADPCM decoder. Supports mono and stereo streams.
ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]
uword[] @split t_step = [
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411,
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
32767]
uword @requirezp predict ; decoded 16 bit pcm sample for first channel.
ubyte @requirezp index
uword @requirezp pstep
sub init(uword startPredict, ubyte startIndex) {
; initialize first decoding channel.
predict = startPredict
index = startIndex
pstep = t_step[index]
}
sub decode_nibble(ubyte @zp nibble) {
; Decoder for a single nibble for the first channel. (value of 'nibble' needs to be strictly 0-15 !)
; This is the hotspot of the decoder algorithm!
; Note that the generated assembly from this is pretty efficient,
; rewriting it by hand in asm seems to improve it only ~10%.
cx16.r0s = 0 ; difference
if nibble & %0100 !=0
cx16.r0s += pstep
pstep >>= 1
if nibble & %0010 !=0
cx16.r0s += pstep
pstep >>= 1
if nibble & %0001 !=0
cx16.r0s += pstep
pstep >>= 1
cx16.r0s += pstep
if nibble & %1000 !=0
predict -= cx16.r0
else
predict += cx16.r0
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
; if predicted > 32767:
; predicted = 32767
; elif predicted < -32767:
; predicted = - 32767
index += t_index[nibble]
if_neg
index = 0
else if index >= len(t_step)-1
index = len(t_step)-1
pstep = t_step[index]
}
}
+111
View File
@@ -0,0 +1,111 @@
%import gfx_lores
%import math
circles {
const ubyte MAX_NUM_CIRCLES = 80
const ubyte GROWTH_RATE = 4
uword[MAX_NUM_CIRCLES] @split circle_x
uword[MAX_NUM_CIRCLES] @split circle_y
ubyte[MAX_NUM_CIRCLES] circle_radius
ubyte color
uword total_num_circles
sub draw(bool use_kernal, uword max_time) -> uword {
if use_kernal
void cx16.set_screen_mode(128)
else
gfx_lores.graphics_mode()
math.rndseed(12345,6789)
cbm.SETTIM(0,0,0)
total_num_circles = 0
color = 16
while cbm.RDTIM16()<max_time {
if use_kernal {
cx16.GRAPH_set_colors(0,0,0)
cx16.GRAPH_clear()
}
else
gfx_lores.clear_screen(0)
total_num_circles += draw_circles(use_kernal, max_time)
}
if use_kernal
void cx16.set_screen_mode(3)
else {
gfx_lores.text_mode()
}
return total_num_circles
}
sub draw_circles(bool use_kernal, uword max_time) -> uword {
uword @zp x
uword @zp y
ubyte @zp radius
ubyte num_circles
while num_circles<MAX_NUM_CIRCLES and cbm.RDTIM16()<max_time {
x = math.rndw() % 320
y = math.rndw() % 240
radius = GROWTH_RATE
if not_colliding() {
while not_edge() and not_colliding() {
radius += GROWTH_RATE
}
radius -= GROWTH_RATE
if radius>0 {
color++
if color==0
color=16
if use_kernal {
cx16.GRAPH_set_colors(color, 255-color, 0)
cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true)
}
else
gfx_lores.disc(x, y as ubyte, radius, color)
circle_x[num_circles] = x
circle_y[num_circles] = y
circle_radius[num_circles] = radius
num_circles++
}
}
}
return num_circles
sub not_colliding() -> bool {
if num_circles==0
return true
ubyte @zp c
for c in 0 to num_circles-1 {
if distance(c) < (radius as uword) + circle_radius[c]
return false
}
return true
}
sub distance(ubyte cix) -> uword {
word dx = x as word - circle_x[cix]
word dy = y as word - circle_y[cix]
uword sqx = dx*dx as uword
uword sqy = dy*dy as uword
return sqrt(sqx + sqy)
}
sub not_edge() -> bool {
if x as word - radius < 0
return false
if x + radius >= 320
return false
if y as word - radius < 0
return false
if y + radius >= 240
return false
return true
}
}
}
+123
View File
@@ -0,0 +1,123 @@
; conway's game of life.
%import math
%import textio
life {
const ubyte WIDTH = 40
const ubyte HEIGHT = 30
const uword STRIDE = $0002+WIDTH
uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
uword @requirezp active_world = world1
sub benchmark(uword max_time) -> uword {
txt.clear_screen()
sys.memset(world1, (WIDTH+2)*(HEIGHT+2), 0)
sys.memset(world2, (WIDTH+2)*(HEIGHT+2), 0)
set_start_gen()
uword gen
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
next_gen()
gen++
}
return gen
}
sub set_start_gen() {
; some way to set a custom start generation:
; str start_gen = " " +
; " " +
; " " +
; " ** " +
; " * * " +
; " * " +
; " * * " +
; " ****** " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " " +
; " "
;
; for y in 0 to 15 {
; for x in 0 to 15 {
; if start_gen[y*16 + x]=='*'
; active_world[offset + x] = 1
; }
; offset += STRIDE
; }
; randomize whole world
math.rndseed(12345,9999)
uword offset = STRIDE+1
ubyte x
ubyte y
for y in 0 to HEIGHT-1 {
for x in 0 to WIDTH-1 {
active_world[offset+x] = math.rnd() & 1
}
offset += STRIDE
}
}
sub next_gen() {
const ubyte DXOFFSET = 0
const ubyte DYOFFSET = 0
ubyte[2] cell_chars = [sc:' ', sc:'●']
uword @requirezp new_world = world1
if active_world == world1
new_world = world2
; To avoid re-calculating word index lookups into the new- and active world arrays,
; we calculate the required pointer values upfront.
; Inside the loop we can use ptr+x just fine (results in efficient LDA (ptr),Y instruction because x is a byte type),
; and for each row we simply add the stride to the pointer.
; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces
; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop.
uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
ubyte x
ubyte y
for y in DYOFFSET to HEIGHT+DYOFFSET-1 {
cx16.vaddr_autoincr(1, $b000 + 256*y, 0, 2) ; allows us to use simple Vera data byte assigns later instead of setchr() calls
for x in DXOFFSET to WIDTH+DXOFFSET-1 {
; count the living neighbors
ubyte cell = @(active_world_ptr + x)
uword @requirezp ptr = active_world_ptr + x - STRIDE - 1
ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) +
@(ptr+STRIDE) + cell + @(ptr+STRIDE+2) +
@(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2)
; apply game of life rules
if neighbors==3
cell=1
else if neighbors!=4
cell=0
@(new_world_ptr + x) = cell
; draw new cell
; txt.setchr(x,y,cell_chars[cell])
cx16.VERA_DATA0 = cell_chars[cell]
}
active_world_ptr += STRIDE
new_world_ptr += STRIDE
}
active_world = new_world
}
}
+54
View File
@@ -0,0 +1,54 @@
%import textio
%import floats
mandelbrot {
const ubyte width = 39
const ubyte height = 29
const ubyte max_iter = 15
sub calc(uword max_time) -> uword {
uword num_pixels
ubyte pixelx
ubyte pixely
txt.home()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16() < max_time {
for pixely in 0 to height-1 {
float yy = (pixely as float)/0.40/height - 1.3
for pixelx in 0 to width-1 {
float xx = (pixelx as float)/0.32/width - 2.2
float xsquared = 0.0
float ysquared = 0.0
float x = 0.0
float y = 0.0
ubyte iter = 0
while iter<max_iter and xsquared+ysquared<4.0 {
y = x*y*2.0 + yy
x = xsquared - ysquared + xx
xsquared = x*x
ysquared = y*y
iter++
}
txt.color2(1, max_iter-iter)
txt.spc()
num_pixels++
if cbm.RDTIM16()>=max_time
goto finished
}
txt.nl()
}
txt.clear_screen()
}
finished:
txt.color2(1, 6)
return num_pixels
}
}
+343
View File
@@ -0,0 +1,343 @@
%import textio
%import math
; Even though prog8 only has support for extremely limited recursion,
; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure.
; This program shows a depth-first maze generation algorithm (1 possible path from start to finish),
; and a depth-first maze solver algorithm, both using a stack to store the path taken.
; Note: this program can be compiled for multiple target systems.
maze {
uword score
sub bench(uword max_time) -> uword {
txt.nl()
score=0
math.rndseed(2345,44332)
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
maze.initialize()
maze.drawStartFinish()
if maze.generate(max_time) {
maze.openpassages()
maze.drawStartFinish()
if maze.solve(max_time) {
maze.drawStartFinish()
} else break
} else break
}
return score
}
const uword screenwidth = 40
const uword screenheight = 30
const ubyte numCellsHoriz = (screenwidth-1) / 2
const ubyte numCellsVert = (screenheight-1) / 2
; maze start and finish cells
const ubyte startCx = 0
const ubyte startCy = 0
const ubyte finishCx = numCellsHoriz-1
const ubyte finishCy = numCellsVert-1
; cell properties
const ubyte STONE = 128
const ubyte WALKED = 64
const ubyte BACKTRACKED = 32
const ubyte UP = 1
const ubyte RIGHT = 2
const ubyte DOWN = 4
const ubyte LEFT = 8
const ubyte WALLCOLOR = 12
const ubyte EMPTYCOLOR = 0
; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore.
uword cells = memory("cells", numCellsHoriz*numCellsVert, 0)
ubyte[256] cx_stack
ubyte[256] cy_stack
ubyte stackptr
ubyte[4] directionflags = [LEFT,RIGHT,UP,DOWN]
sub generate(uword max_time) -> bool {
ubyte cx = startCx
ubyte cy = startCy
stackptr = 0
@(celladdr(cx,cy)) &= ~STONE
drawCell(cx, cy)
uword cells_to_carve = numCellsHoriz * numCellsVert - 1
while cbm.RDTIM16()<max_time {
carve_restart_after_repath:
ubyte direction = choose_uncarved_direction()
if direction==0 {
;backtrack
stackptr--
if stackptr==255 {
; stack empty.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
}
return true
}
cx = cx_stack[stackptr]
cy = cy_stack[stackptr]
} else {
cx_stack[stackptr] = cx
cy_stack[stackptr] = cy
stackptr++
if stackptr==0 {
; stack overflow, we can't track our path any longer.
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
if cells_to_carve!=0 {
if repath()
goto carve_restart_after_repath
}
return true
}
@(celladdr(cx,cy)) |= direction
when direction {
UP -> {
cy--
@(celladdr(cx,cy)) |= DOWN
}
RIGHT -> {
cx++
@(celladdr(cx,cy)) |= LEFT
score++
}
DOWN -> {
cy++
@(celladdr(cx,cy)) |= UP
}
LEFT -> {
cx--
@(celladdr(cx,cy)) |= RIGHT
}
}
@(celladdr(cx,cy)) &= ~STONE
cells_to_carve--
drawCell(cx, cy)
}
}
return false
sub repath() -> bool {
; repath: try to find a new start cell with possible directions.
; we limit our number of searches so that the algorith doesn't get stuck
; for too long on bad rng... just accept a few unused cells in that case.
repeat 255 {
do {
cx = math.rnd() % numCellsHoriz
cy = math.rnd() % numCellsVert
} until @(celladdr(cx, cy)) & STONE ==0
if available_uncarved()!=0
return true
}
return false
}
sub available_uncarved() -> ubyte {
ubyte candidates = 0
if cx>0 and @(celladdr(cx-1, cy)) & STONE !=0
candidates |= LEFT
if cx<numCellsHoriz-1 and @(celladdr(cx+1, cy)) & STONE !=0
candidates |= RIGHT
if cy>0 and @(celladdr(cx, cy-1)) & STONE !=0
candidates |= UP
if cy<numCellsVert-1 and @(celladdr(cx, cy+1)) & STONE !=0
candidates |= DOWN
return candidates
}
sub choose_uncarved_direction() -> ubyte {
ubyte candidates = available_uncarved()
if candidates==0
return 0
repeat {
ubyte choice = candidates & directionflags[math.rnd() & 3]
if choice!=0
return choice
}
}
}
sub openpassages() {
; open just a few extra passages, so that multiple routes are possible in theory.
ubyte numpassages
ubyte cx
ubyte cy
do {
do {
cx = math.rnd() % (numCellsHoriz-2) + 1
cy = math.rnd() % (numCellsVert-2) + 1
} until @(celladdr(cx, cy)) & STONE ==0
ubyte direction = directionflags[math.rnd() & 3]
if @(celladdr(cx, cy)) & direction == 0 {
when direction {
LEFT -> {
if @(celladdr(cx-1,cy)) & STONE == 0 {
@(celladdr(cx,cy)) |= LEFT
drawCell(cx,cy)
numpassages++
}
}
RIGHT -> {
if @(celladdr(cx+1,cy)) & STONE == 0 {
@(celladdr(cx,cy)) |= RIGHT
drawCell(cx,cy)
numpassages++
}
}
UP -> {
if @(celladdr(cx,cy-1)) & STONE == 0 {
@(celladdr(cx,cy)) |= UP
drawCell(cx,cy)
numpassages++
}
}
DOWN -> {
if @(celladdr(cx,cy+1)) & STONE == 0 {
@(celladdr(cx,cy)) |= DOWN
drawCell(cx,cy)
numpassages++
}
}
}
}
} until numpassages==10
}
sub solve(uword max_time) -> bool {
ubyte cx = startCx
ubyte cy = startCy
const uword max_path_length = 1024
; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :(
uword pathstack = memory("pathstack", max_path_length, 0)
uword pathstackptr = 0
@(celladdr(cx,cy)) |= WALKED
; txt.setcc(cx*2+1, cy*2+1, 81, 1)
while cbm.RDTIM16()<max_time {
solve_loop:
if cx==finishCx and cy==finishCy {
;txt.home()
txt.print("found! path length: ")
txt.print_uw(pathstackptr)
txt.nl()
return true
}
ubyte cell = @(celladdr(cx,cy))
if cell & UP!=0 and @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = UP
;txt.setcc(cx*2+1, cy*2, 81, 3)
cy--
}
else if cell & DOWN !=0 and @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = DOWN
;txt.setcc(cx*2+1, cy*2+2, 81, 3)
cy++
}
else if cell & LEFT !=0 and @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = LEFT
;txt.setcc(cx*2, cy*2+1, 81, 3)
cx--
}
else if cell & RIGHT !=0 and @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) ==0 {
@(pathstack + pathstackptr) = RIGHT
;txt.setcc(cx*2+2, cy*2+1, 81, 3)
cx++
}
else {
; dead end, pop stack
pathstackptr--
if pathstackptr==65535 {
txt.print("no solution?!\n")
return true
}
@(celladdr(cx,cy)) |= BACKTRACKED
;txt.setcc(cx*2+1, cy*2+1, 81, 2)
when @(pathstack + pathstackptr) {
UP -> {
;txt.setcc(cx*2+1, cy*2+2, 81, 9)
cy++
}
DOWN -> {
;txt.setcc(cx*2+1, cy*2, 81, 9)
cy--
}
LEFT -> {
;txt.setcc(cx*2+2, cy*2+1, 81, 9)
cx++
}
RIGHT -> {
;txt.setcc(cx*2, cy*2+1, 81, 9)
cx--
score++
}
}
goto solve_loop
}
pathstackptr++
if pathstackptr==max_path_length {
txt.print("stack overflow, path too long\n")
return true
}
@(celladdr(cx,cy)) |= WALKED
;txt.setcc(cx*2+1, cy*2+1, 81, 1)
}
return false
}
sub celladdr(ubyte cx, ubyte cy) -> uword {
return cells+(numCellsHoriz as uword)*cy+cx
}
sub drawCell(ubyte cx, ubyte cy) {
return
; ubyte x = cx * 2 + 1
; ubyte y = cy * 2 + 1
; ubyte doors = @(celladdr(cx,cy))
; if doors & UP !=0
; txt.setcc(x, y-1, ' ', EMPTYCOLOR)
; if doors & RIGHT !=0
; txt.setcc(x+1, y, ' ', EMPTYCOLOR)
; if doors & DOWN !=0
; txt.setcc(x, y+1, ' ', EMPTYCOLOR)
; if doors & LEFT !=0
; txt.setcc(x-1, y, ' ', EMPTYCOLOR)
; if doors & STONE !=0
; txt.setcc(x, y, 160, WALLCOLOR)
; else
; txt.setcc(x, y, 32, EMPTYCOLOR)
;
; if doors & WALKED !=0
; txt.setcc(x, y, 81, 1)
; if doors & BACKTRACKED !=0
; txt.setcc(x, y, 81, 2)
}
sub initialize() {
sys.memset(cells, numCellsHoriz*numCellsVert, STONE)
; txt.fill_screen(160, WALLCOLOR)
drawStartFinish()
}
sub drawStartFinish() {
; txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5)
; txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13)
}
}
+63
View File
@@ -0,0 +1,63 @@
%import textio
; Recursive N-Queens solver.
; The problem is: find all possible ways to place 8 Queen chess pieces on a chess board, so that none of them attacks any other.
; (this program prints all solutions without taking mirroring and flipping the chess board into account)
; Note: this program can be compiled for multiple target systems.
queens {
const ubyte NUMQUEENS=8
ubyte[NUMQUEENS] board
sub could_place(ubyte row, ubyte col) -> bool {
if row==0
return true
ubyte i
for i in 0 to row-1 {
if board[i]==col or board[i]-i==col-row or board[i]+i==col+row
return false
}
return true
}
uword solution_count
uword maximum_duration
sub place_queen(ubyte row) -> bool {
if row == NUMQUEENS {
solution_count++
txt.chrout('.')
return cbm.RDTIM16()<maximum_duration
}
bool continue_running=true
ubyte col
for col in 0 to NUMQUEENS-1 {
if could_place(row, col) {
board[row] = col
; we need to save the local variables row and col.
sys.push(row)
sys.push(col)
continue_running = place_queen(row + 1)
; restore the local variables after the recursive call.
col = sys.pop()
row = sys.pop()
board[row] = 0
if not continue_running
break
}
}
return continue_running
}
sub bench(uword max_time) -> uword {
solution_count = 0
maximum_duration = max_time
txt.nl()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16() < maximum_duration {
void place_queen(0)
}
return solution_count
}
}
+990
View File
@@ -0,0 +1,990 @@
%import textio
%import conv
%import string
%import string
textelite {
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
const ubyte numforZaonce = 129
const ubyte numforDiso = 147
const ubyte numforRiedquat = 46
uword num_commands
sub bench(uword max_time) -> uword {
num_commands = 0
txt.lowercase()
cbm.SETTIM(0,0,0)
while cbm.RDTIM16()<max_time {
reinit()
run_commands(max_time)
}
return num_commands
}
sub reinit() {
;txt.clear_screen()
;txt.print("\n --- TextElite v1.3 ---\n")
txt.print("\nnew game\n")
elite_planet.set_seed(0, 0)
elite_galaxy.travel_to(1, numforLave)
elite_market.init(0) ; Lave's market is seeded with 0
elite_ship.init()
elite_planet.display(false, 0)
input_index = 0
}
sub run_commands(uword max_time) {
while cbm.RDTIM16() < max_time {
str input = "????????"
;txt.print("\nCash: ")
;elite_util.print_10s(elite_ship.cash)
;txt.print("\nCommand (?=help): ")
ubyte num_chars = next_input(input)
;txt.nl()
if num_chars!=0 {
when input[0] {
'q' -> {
bool has_error = false
if elite_galaxy.number != 2 {
txt.print("\nERROR: galaxy is not 2: ")
txt.print_ub(elite_galaxy.number)
txt.nl()
has_error=true
}
if elite_planet.number != 164 {
txt.print("\nERROR: planet is not 164: ")
txt.print_ub(elite_planet.number)
txt.nl()
has_error=true
}
if elite_planet.x != 116 {
txt.print("\nERROR: planet.x is not 116: ")
txt.print_ub(elite_planet.x)
txt.nl()
has_error=true
}
if elite_planet.y != 201 {
txt.print("\nERROR: planet.y is not 201: ")
txt.print_ub(elite_planet.y)
txt.nl()
has_error=true
}
if "ribeen" != elite_planet.name {
txt.print("\nERROR: planet.name is not 'ribeen': ")
txt.print(elite_planet.name)
txt.nl()
has_error=true
}
if elite_ship.cash != 1212 {
txt.print("\nERROR: cash is not 1212: ")
txt.print_uw(elite_ship.cash)
txt.nl()
has_error=true
}
if elite_ship.fuel != 50 {
txt.print("\nERROR: fuel is not 50:")
txt.print_ub(elite_ship.fuel)
txt.nl()
has_error=true
}
if elite_ship.cargohold[0] != 3 {
txt.print("\nERROR: food is not 3:")
txt.print_ub(elite_ship.cargohold[0])
txt.nl()
has_error=true
}
if elite_ship.cargohold[1] != 0 {
txt.print("\nERROR: textiles is not 0:")
txt.print_ub(elite_ship.cargohold[1])
txt.nl()
has_error=true
}
if has_error
sys.exit(1)
return
}
'b' -> elite_trader.do_buy()
's' -> elite_trader.do_sell()
'f' -> elite_trader.do_fuel()
'j' -> elite_trader.do_jump()
't' -> elite_trader.do_teleport()
'g' -> elite_trader.do_next_galaxy()
'i' -> elite_trader.do_info()
'm' -> {
if input[1]=='a' and input[2]=='p'
elite_trader.do_map()
else
elite_trader.do_show_market()
}
'l' -> elite_trader.do_local()
'c' -> elite_trader.do_cash()
'h' -> elite_trader.do_hold()
}
num_commands++
}
}
}
str[] inputs = [
"i",
"diso",
"i",
"lave",
"m",
"b",
"food",
"15",
"map",
"g",
"map",
"l",
"j",
"zao",
"s",
"food",
"12",
"tele",
"quti",
"tele",
"aro",
"i",
"diso",
"i",
"lave",
"i",
"zao",
"galhyp",
"fuel",
"20",
"j",
"rib",
"i",
"rib",
"i",
"tiri",
"q",
0
]
ubyte input_index
sub next_input(str buffer) -> ubyte {
input_index++
return string.copy(inputs[input_index], buffer)
}
}
elite_trader {
str input = "??????????"
ubyte num_chars
sub do_jump() {
;txt.print("\nJump to what system? ")
jump_to_system()
}
sub do_teleport() {
;txt.print("\nCheat! Teleport to what system? ")
ubyte fuel = elite_ship.fuel
elite_ship.fuel = 255
jump_to_system()
elite_ship.fuel = fuel
}
sub jump_to_system() {
void textelite.next_input(input)
ubyte current_planet = elite_planet.number
ubyte x = elite_planet.x
ubyte y = elite_planet.y
if elite_galaxy.search_closest_planet(input) {
ubyte distance = elite_planet.distance(x, y)
if distance <= elite_ship.fuel {
elite_galaxy.init_market_for_planet()
elite_ship.fuel -= distance
;txt.print("\n\nHyperspace jump! Arrived at:\n")
elite_planet.display(true,0 )
return
}
;txt.print("\nInsufficient fuel\n")
} else {
;txt.print(" Not found!\n")
}
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
}
sub do_buy() {
;txt.print("\nBuy what commodity? ")
str commodity = "???????????????"
void textelite.next_input(commodity)
ubyte ci = elite_market.match(commodity)
if ci & 128 !=0 {
txt.print("Unknown\n")
} else {
;txt.print("\nHow much? ")
void textelite.next_input(input)
ubyte amount = conv.str2ubyte(input)
if elite_market.current_quantity[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = elite_market.current_price[ci] * amount
;txt.print(" Total price: ")
;elite_util.print_10s(price)
if price > elite_ship.cash {
txt.print(" Not enough cash!\n")
} else {
elite_ship.cash -= price
elite_ship.cargohold[ci] += amount
elite_market.current_quantity[ci] -= amount
}
}
}
}
sub do_sell() {
;txt.print("\nSell what commodity? ")
str commodity = "???????????????"
void textelite.next_input(commodity)
ubyte ci = elite_market.match(commodity)
if ci & 128 !=0 {
txt.print("Unknown\n")
} else {
;txt.print("\nHow much? ")
void textelite.next_input(input)
ubyte amount = conv.str2ubyte(input)
if elite_ship.cargohold[ci] < amount {
txt.print(" Insufficient supply!\n")
} else {
uword price = elite_market.current_price[ci] * amount
;txt.print(" Total price: ")
;elite_util.print_10s(price)
elite_ship.cash += price
elite_ship.cargohold[ci] -= amount
elite_market.current_quantity[ci] += amount
}
}
}
sub do_fuel() {
;txt.print("\nBuy fuel. Amount? ")
void textelite.next_input(input)
ubyte buy_fuel = 10*conv.str2ubyte(input)
ubyte max_fuel = elite_ship.Max_fuel - elite_ship.fuel
if buy_fuel > max_fuel
buy_fuel = max_fuel
uword price = buy_fuel as uword * elite_ship.Fuel_cost
if price > elite_ship.cash {
txt.print("Not enough cash!\n")
} else {
elite_ship.cash -= price
elite_ship.fuel += buy_fuel
}
}
sub do_cash() {
;txt.print("\nCheat! Set cash amount: ")
void textelite.next_input(input)
elite_ship.cash = conv.str2uword(input)
}
sub do_hold() {
;txt.print("\nCheat! Set cargohold size: ")
void textelite.next_input(input)
elite_ship.Max_cargo = conv.str2ubyte(input)
}
sub do_next_galaxy() {
txt.print("\n>>>>> Galaxy Hyperjump!\n")
elite_galaxy.travel_to(elite_galaxy.number+1, elite_planet.number)
elite_planet.display(false, 0)
}
sub do_info() {
;txt.print("\nSystem name (empty=current): ")
num_chars = textelite.next_input(input)
if num_chars!=0 {
ubyte current_planet = elite_planet.number
ubyte x = elite_planet.x
ubyte y = elite_planet.y
if elite_galaxy.search_closest_planet(input) {
ubyte distance = elite_planet.distance(x, y)
elite_planet.display(false, distance)
} else {
;txt.print(" Not found!")
}
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
} else {
elite_planet.display(false, 0)
}
}
sub do_local() {
elite_galaxy.local_area()
}
sub do_map() {
;txt.print("\n(l)ocal or (g)alaxy starmap? ")
num_chars = textelite.next_input(input)
if num_chars!=0 {
elite_galaxy.starmap(input[0]=='l')
}
}
sub do_show_market() {
elite_market.display()
;txt.print("\nFuel: ")
;elite_util.print_10s(elite_ship.fuel)
;txt.print(" Cargohold space: ")
;txt.print_ub(elite_ship.cargo_free())
;txt.print("t\n")
}
}
elite_ship {
const ubyte Max_fuel = 70
const ubyte Fuel_cost = 2
ubyte Max_cargo = 20
ubyte fuel
uword cash
ubyte[17] cargohold
sub init() {
sys.memset(cargohold, len(cargohold), 0)
fuel = Max_fuel
cash = 1000
}
}
elite_market {
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
ubyte[17] current_quantity
uword[17] current_price
sub init(ubyte fluct) {
; Prices and availabilities are influenced by the planet's economy type
; (0-7) and a random "fluctuation" byte that was kept within the saved
; commander position to keep the market prices constant over gamesaves.
; Availabilities must be saved with the game since the player alters them
; by buying (and selling(?))
;
; Almost all commands are one byte only and overflow "errors" are
; extremely frequent and exploited.
;
; Trade Item prices are held internally in a single byte=true value/4.
; The decimal point in prices is introduced only when printing them.
; Internally, all prices are integers.
; The player's cash is held in four bytes.
ubyte ci
for ci in 0 to len(names)-1 {
word product
byte changing
product = elite_planet.economy as word * gradients[ci]
changing = fluct & maskbytes[ci] as byte
ubyte q = (basequants[ci] as word + changing - product) as ubyte
if q & $80 !=0
q = 0 ; clip to positive 8-bit
current_quantity[ci] = q & $3f
q = (baseprices[ci] + changing + product) as ubyte
current_price[ci] = q * $0004
}
current_quantity[16] = 0 ; force nonavailability of Alien Items
}
sub display() {
return
; ubyte ci
; txt.nl()
; elite_planet.print_name_uppercase()
; txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
; for ci in 0 to len(names)-1 {
; elite_util.print_right(13, names[ci])
; txt.print(" ")
; elite_util.print_10s(current_price[ci])
; txt.column(24)
; txt.print_ub(current_quantity[ci])
; txt.chrout(' ')
; when units[ci] {
; 0 -> txt.chrout('t')
; 1 -> txt.print("kg")
; 2 -> txt.chrout('g')
; }
; txt.column(32)
; txt.print_ub(elite_ship.cargohold[ci])
; txt.nl()
; }
}
sub match(uword nameptr) -> ubyte {
ubyte ci
for ci in 0 to len(names)-1 {
if elite_util.prefix_matches(nameptr, names[ci])
return ci
}
return 255
}
}
elite_galaxy {
const uword GALSIZE = 256
const uword base0 = $5A4A ; seeds for the first galaxy
const uword base1 = $0248
const uword base2 = $B753
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
ubyte number
uword[3] seed
sub init(ubyte galaxynum) {
number = 1
elite_planet.number = 255
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxynum-1 {
nextgalaxy()
}
}
sub nextgalaxy() {
textelite.num_commands++
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
}
sub travel_to(ubyte galaxynum, ubyte system) {
init(galaxynum)
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet()
textelite.num_commands++
}
elite_planet.name = make_current_planet_name()
init_market_for_planet()
}
sub init_market_for_planet() {
elite_market.init(lsb(seed[0])+msb(seed[2]))
}
sub search_closest_planet(uword nameptr) -> bool {
textelite.num_commands++
ubyte x = elite_planet.x
ubyte y = elite_planet.y
ubyte current_planet_num = elite_planet.number
init(number)
bool found = false
ubyte current_closest_pi
ubyte current_distance = 127
ubyte pi
for pi in 0 to 255 {
generate_next_planet()
elite_planet.name = make_current_planet_name()
if elite_util.prefix_matches(nameptr, elite_planet.name) {
ubyte distance = elite_planet.distance(x, y)
if distance < current_distance {
current_distance = distance
current_closest_pi = pi
found = true
}
}
}
if found
travel_to(number, current_closest_pi)
else
travel_to(number, current_planet_num)
return found
}
sub local_area() {
ubyte current_planet = elite_planet.number
ubyte px = elite_planet.x
ubyte py = elite_planet.y
ubyte pn = 0
init(number)
; txt.print("\nGalaxy #")
; txt.print_ub(number)
; txt.print(" - systems in vicinity:\n")
do {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= elite_ship.Max_fuel {
; if distance <= elite_ship.fuel
; txt.chrout('*')
; else
; txt.chrout('-')
; txt.spc()
elite_planet.name = make_current_planet_name()
elite_planet.display(true, distance)
}
pn++
} until pn==0
travel_to(number, current_planet)
}
sub starmap(bool local) {
ubyte current_planet = elite_planet.number
ubyte px = elite_planet.x
ubyte py = elite_planet.y
str current_name = " " ; 8 max
ubyte pn = 0
current_name = elite_planet.name
init(number)
; txt.clear_screen()
; txt.print("Galaxy #")
; txt.print_ub(number)
; if local
; txt.print(" - local systems")
; else
; txt.print(" - galaxy")
; txt.print(" starmap:\n")
ubyte max_distance = 255
if local
max_distance = elite_ship.Max_fuel
ubyte home_sx
ubyte home_sy
ubyte home_distance
do {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= max_distance {
elite_planet.name = make_current_planet_name()
elite_planet.name[0] = string.upperchar(elite_planet.name[0])
uword tx = elite_planet.x
uword ty = elite_planet.y
if local {
tx = tx + 24 - px
ty = ty + 24 - py
}
ubyte sx = display_scale_x(tx)
ubyte sy = display_scale_y(ty)
ubyte char = '*'
if elite_planet.number==current_planet
char = '%'
if local {
print_planet_details(elite_planet.name, sx, sy, distance)
} else if elite_planet.number==current_planet {
home_distance = distance
home_sx = sx
home_sy = sy
}
; txt.setchr(2+sx, 2+sy, char)
}
pn++
} until pn==0
if not local
print_planet_details(current_name, home_sx, home_sy, home_distance)
; if local
; txt.plot(0, display_scale_y(64) + 4)
; else
; txt.plot(0, display_scale_y(256) + 4 as ubyte)
travel_to(number, current_planet)
sub print_planet_details(str name, ubyte screenx, ubyte screeny, ubyte d) {
return
; txt.plot(2+screenx-2, 2+screeny+1)
; txt.print(name)
; if d!=0 {
; txt.plot(2+screenx-2, 2+screeny+2)
; elite_util.print_10s(d)
; txt.print(" LY")
; }
}
sub display_scale_x(uword x) -> ubyte {
if local
return x/2 as ubyte
return x/8 as ubyte
}
sub display_scale_y(uword y) -> ubyte {
if local
return y/4 as ubyte
return y/16 as ubyte
}
}
ubyte pn_pair1
ubyte pn_pair2
ubyte pn_pair3
ubyte pn_pair4
bool longname
sub generate_next_planet() {
determine_planet_properties()
longname = lsb(seed[0]) & 64 !=0
; Always four iterations of random number
pn_pair1 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair2 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair3 = (msb(seed[2]) & 31) * 2
tweakseed()
pn_pair4 = (msb(seed[2]) & 31) * 2
tweakseed()
}
sub make_current_planet_name() -> str {
ubyte ni = 0
str name = " " ; max 8
if pn_pairs[pn_pair1] != '.' {
name[ni] = pn_pairs[pn_pair1]
ni++
}
if pn_pairs[pn_pair1+1] != '.' {
name[ni] = pn_pairs[pn_pair1+1]
ni++
}
if pn_pairs[pn_pair2] != '.' {
name[ni] = pn_pairs[pn_pair2]
ni++
}
if pn_pairs[pn_pair2+1] != '.' {
name[ni] = pn_pairs[pn_pair2+1]
ni++
}
if pn_pairs[pn_pair3] != '.' {
name[ni] = pn_pairs[pn_pair3]
ni++
}
if pn_pairs[pn_pair3+1] != '.' {
name[ni] = pn_pairs[pn_pair3+1]
ni++
}
if longname {
if pn_pairs[pn_pair4] != '.' {
name[ni] = pn_pairs[pn_pair4]
ni++
}
if pn_pairs[pn_pair4+1] != '.' {
name[ni] = pn_pairs[pn_pair4+1]
ni++
}
}
name[ni] = 0
return name
}
sub determine_planet_properties() {
; create the planet's characteristics
elite_planet.number++
elite_planet.x = msb(seed[1])
elite_planet.y = msb(seed[0])
elite_planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
elite_planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
if elite_planet.govtype <= 1
elite_planet.economy = (elite_planet.economy | 2)
elite_planet.techlevel = (msb(seed[1]) & 3) + (elite_planet.economy ^ 7)
elite_planet.techlevel += elite_planet.govtype >> 1
if elite_planet.govtype & 1 !=0
elite_planet.techlevel++
elite_planet.population = 4 * elite_planet.techlevel + elite_planet.economy
elite_planet.population += elite_planet.govtype + 1
elite_planet.productivity = ((elite_planet.economy ^ 7) + 3) * (elite_planet.govtype + 4)
elite_planet.productivity *= elite_planet.population * 8
ubyte seed2_msb = msb(seed[2])
elite_planet.radius = mkword((seed2_msb & 15) + 11, elite_planet.x)
elite_planet.species_is_alien = lsb(seed[2]) & 128 !=0 ; bit 7 of w2_lo
if elite_planet.species_is_alien {
elite_planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
elite_planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
elite_planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
elite_planet.species_kind = (elite_planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
elite_planet.goatsoup_seed[0] = lsb(seed[1])
elite_planet.goatsoup_seed[1] = msb(seed[1])
elite_planet.goatsoup_seed[2] = lsb(seed[2])
elite_planet.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {
uword temp = seed[0] + seed[1] + seed[2]
seed[0] = seed[1]
seed[1] = seed[2]
seed[2] = temp
}
sub twist(uword x) -> uword {
ubyte xh = msb(x)
ubyte xl = lsb(x)
xh <<= 1 ; make sure carry flag is not used on first shift!
rol(xl)
return mkword(xh, xl)
}
}
elite_planet {
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
str[] words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
str[] words91 = ["planet", "world", "place", "little planet", "dump"]
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
uword[] @shared wordlists = [
words81, words82, words83, words84, words85, words86, words87, words88,
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
words91, words92, words93, words94, words95, words96, words97, words98,
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
wordsA1, wordsA2, wordsA3, wordsA4]
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
str name = " " ; 8 max
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
ubyte x
ubyte y
ubyte economy
ubyte govtype
ubyte techlevel
ubyte population
uword productivity
uword radius
bool species_is_alien ; otherwise "Human Colonials"
ubyte species_size
ubyte species_color
ubyte species_look
ubyte species_kind
sub set_seed(uword s1, uword s2) {
goatsoup_seed[0] = lsb(s1)
goatsoup_seed[1] = msb(s1)
goatsoup_seed[2] = lsb(s2)
goatsoup_seed[3] = msb(s2)
reset_rnd()
}
sub reset_rnd() {
goatsoup_rnd[0] = goatsoup_seed[0]
goatsoup_rnd[1] = goatsoup_seed[1]
goatsoup_rnd[2] = goatsoup_seed[2]
goatsoup_rnd[3] = goatsoup_seed[3]
}
sub random_name() -> str {
ubyte ii
str randname = " " ; 8 chars max
ubyte nx = 0
for ii in 0 to goatsoup_rnd_number() & 3 {
ubyte xx = goatsoup_rnd_number() & $3e
if pairs0[xx] != '.' {
randname[nx] = pairs0[xx]
nx++
}
xx++
if pairs0[xx] != '.' {
randname[nx] = pairs0[xx]
nx++
}
}
randname[nx] = 0
randname[0] = string.upperchar(randname[0])
return randname
}
sub goatsoup_rnd_number() -> ubyte {
ubyte xx = goatsoup_rnd[0] * 2
uword a = xx as uword + goatsoup_rnd[2]
if goatsoup_rnd[0] > 127
a ++
goatsoup_rnd[0] = lsb(a)
goatsoup_rnd[2] = xx
xx = goatsoup_rnd[1]
ubyte ac = xx + goatsoup_rnd[3] + msb(a)
goatsoup_rnd[1] = ac
goatsoup_rnd[3] = xx
return ac
}
sub distance(ubyte px, ubyte py) -> ubyte {
uword ax
uword ay
if px>x
ax=px-x
else
ax=x-px
if py>y
ay=py-y
else
ay=y-py
ay /= 2
ubyte d = sqrt(ax*ax + ay*ay)
if d>63
return 255
return d*4
}
sub soup() -> str {
str planet_result = " " * 160
uword[6] source_stack
ubyte stack_ptr = 0
str start_source = "\x8F is \x97."
uword source_ptr = &start_source
uword result_ptr = &planet_result
reset_rnd()
recursive_soup()
return planet_result
sub recursive_soup() {
repeat {
ubyte c = @(source_ptr)
source_ptr++
if c == $00 {
@(result_ptr) = 0
return
}
else if c <= $80 {
@(result_ptr) = c
result_ptr++
}
else {
if c <= $a4 {
ubyte rnr = goatsoup_rnd_number()
ubyte wordNr = ((rnr >= $33) as ubyte) + ((rnr >= $66) as ubyte) + ((rnr >= $99) as ubyte) + ((rnr >= $CC) as ubyte)
source_stack[stack_ptr] = source_ptr
stack_ptr++
source_ptr = getword(c, wordNr)
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
if c == $b0 {
@(result_ptr) = string.upperchar(name[0])
result_ptr++
concat_string(&name + 1)
}
else if c == $b1 {
@(result_ptr) = string.upperchar(name[0])
result_ptr++
ubyte ni
for ni in 1 to len(name) {
ubyte cc = name[ni]
if cc in ['e', 'o', 0]
break
else {
@(result_ptr) = cc
result_ptr++
}
}
@(result_ptr) = 'i'
result_ptr++
@(result_ptr) = 'a'
result_ptr++
@(result_ptr) = 'n'
result_ptr++
}
else if c == $b2 {
concat_string(random_name())
}
else {
@(result_ptr) = c
result_ptr++
}
}
}
}
}
sub concat_string(uword str_ptr) {
repeat {
ubyte c = @(str_ptr)
if c==0
break
else {
@(result_ptr) = c
str_ptr++
result_ptr++
}
}
}
}
sub display(bool compressed, ubyte distance) {
txt.print(soup())
txt.nl()
}
sub getword(ubyte listnum, ubyte wordidx) -> uword {
uword list = wordlists[listnum-$81]
return peekw(list + wordidx*2)
}
}
elite_util {
sub prefix_matches(uword prefixptr, uword stringptr) -> bool {
repeat {
ubyte pc = @(prefixptr)
ubyte sc = @(stringptr)
if pc == 0
return true
; to lowercase for case insensitive compare:
if string.lowerchar(pc)!=string.lowerchar(sc)
return false
prefixptr++
stringptr++
}
}
}
+112
View File
@@ -0,0 +1,112 @@
; This benchmark program is meant to check for regressions in the
; Prog8 compiler's code-generator (performance wise).
;
; As the X16 computer is a more or less fixed system, it's not very useful
; to benchmark the computer itself with.
%import textio
%import b_adpcm
%import b_circles
%import b_3d
%import b_life
%import b_mandelbrot
%import b_queens
%import b_textelite
%import b_maze
%zeropage basicsafe
%option no_sysinit
main {
str[20] benchmark_names
uword[20] benchmark_score
sub start() {
ubyte benchmark_number
void cx16.set_screen_mode(3)
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n")
sys.wait(60)
benchmark_number = 0
announce_benchmark("maze solver")
benchmark_score[benchmark_number] = maze.bench(300)
benchmark_number++
announce_benchmark("n-queens")
benchmark_score[benchmark_number] = queens.bench(300)
benchmark_number++
announce_benchmark("mandelbrot (floating point)")
benchmark_score[benchmark_number] = mandelbrot.calc(400)
benchmark_number++
announce_benchmark("game of life")
benchmark_score[benchmark_number] = life.benchmark(300)
benchmark_number++
announce_benchmark("3d model rotation")
benchmark_score[benchmark_number] = rotate3d.benchmark(300)
benchmark_number++
announce_benchmark("adpcm audio decoding")
benchmark_score[benchmark_number] = adpcm.decode_benchmark(300)
benchmark_number++
announce_benchmark("circles with gfx_lores")
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
void cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
void cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
txt.clear_screen()
txt.plot(4, 6)
txt.print(benchmark_names[benchmark_number])
txt.nl()
sys.wait(60)
}
}
}
+44 -20
View File
@@ -1,5 +1,6 @@
package prog8.code
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram
import prog8.code.core.*
@@ -97,7 +98,7 @@ enum class StNodeType {
// MODULE, // not used with current scoping rules
BLOCK,
SUBROUTINE,
ROMSUB,
EXTSUB,
LABEL,
STATICVAR,
MEMVAR,
@@ -178,31 +179,48 @@ open class StNode(val name: String,
class StStaticVariable(name: String,
val dt: DataType,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?,
val initializationStringValue: StString?,
val initializationArrayValue: StArray?,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish, // used in the variable allocator
val align: Int,
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
var initializationNumericValue: Double? = null
private set
fun setOnetimeInitNumeric(number: Double) {
// In certain cases the init value of an existing var should be updated,
// so we can't ask this as a constructor parameter.
// This has to do with the way Prog8 does the (re)initialization of such variables: via code assignment statements.
// Certain codegens might want to put them back into the variable directly.
// For strings and arrays this doesn't occur - these are always already specced at creation time.
initializationNumericValue = number
}
val uninitialized: Boolean
get() = initializationArrayValue==null && initializationStringValue==null && initializationNumericValue==null
init {
if(length!=null) {
require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null)
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
require(initializationNumericValue == null)
if(initializationArrayValue!=null)
require(initializationArrayValue.isEmpty() ||initializationArrayValue.size==length)
}
if(onetimeInitializationNumericValue!=null) {
if(initializationNumericValue!=null) {
require(dt in NumericDatatypes || dt==DataType.BOOL)
}
if(onetimeInitializationArrayValue!=null) {
if(initializationArrayValue!=null) {
require(dt in ArrayDatatypes)
require(length==onetimeInitializationArrayValue.size)
require(length==initializationArrayValue.size)
}
if(onetimeInitializationStringValue!=null) {
if(initializationStringValue!=null) {
require(dt == DataType.STR)
require(length == onetimeInitializationStringValue.first.length+1)
require(length == initializationStringValue.first.length+1)
}
if(align > 0) {
require(dt == DataType.STR || dt in ArrayDatatypes)
require(zpwish != ZeropageWish.REQUIRE_ZEROPAGE && zpwish != ZeropageWish.PREFER_ZEROPAGE)
}
}
}
@@ -239,18 +257,24 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
StNode(name, StNodeType.SUBROUTINE, astNode)
class StRomSub(name: String,
val address: UInt?, // null in case of asmsub, specified in case of romsub
val parameters: List<StRomSubParameter>,
val returns: List<StRomSubParameter>,
class StExtSub(name: String,
val address: PtAsmSub.Address?, // null in case of asmsub, specified in case of extsub.
val parameters: List<StExtSubParameter>,
val returns: List<StExtSubParameter>,
astNode: PtNode) :
StNode(name, StNodeType.ROMSUB, astNode)
StNode(name, StNodeType.EXTSUB, astNode)
class StSubroutineParameter(val name: String, val type: DataType)
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?)
class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) {
init {
if(number!=null) require(addressOfSymbol==null && boolean==null)
if(addressOfSymbol!=null) require(number==null && boolean==null)
if(boolean!=null) require(addressOfSymbol==null && number==null)
}
}
typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement>
+20 -15
View File
@@ -3,7 +3,7 @@ package prog8.code
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.VMTarget
import java.util.*
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
fun make(): SymbolTable {
@@ -13,8 +13,8 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
}
val scopestack = Stack<StNode>()
scopestack.push(st)
val scopestack = ArrayDeque<StNode>()
scopestack.add(st)
program.children.forEach {
addToSt(it, scopestack)
}
@@ -35,12 +35,12 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
return st
}
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
private fun addToSt(node: PtNode, scope: ArrayDeque<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
StRomSub(node.name, node.address, parameters, returns, node)
val parameters = node.parameters.map { StExtSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StExtSubParameter(it.first, it.second) }
StExtSub(node.name, node.address, parameters, returns, node)
}
is PtBlock -> {
StNode(node.name, StNodeType.BLOCK, node)
@@ -65,23 +65,26 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val numElements: Int?
val value = node.value
if(value!=null) {
val number = (value as? PtNumber)?.number
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
when (value) {
is PtString -> {
initialString = StString(value.value, value.encoding)
initialArray = null
initialNumeric = null
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
initialArray = makeInitialArray(value)
initialString = null
initialNumeric = null
numElements = initialArray.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
require(value is PtNumber)
initialString = null
initialArray = null
val number = value.number
initialNumeric = number
numElements = node.arraySize?.toInt()
}
}
@@ -94,7 +97,10 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
// if(node.type in SplitWordArrayTypes) {
// ... split array also add _lsb and _msb to symboltable?
// }
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
val stVar = StStaticVariable(node.name, node.type, initialString, initialArray, numElements, node.zeropage, node.align.toInt(), node)
if(initialNumeric!=null)
stVar.setOnetimeInitNumeric(initialNumeric)
stVar
}
is PtBuiltinFunctionCall -> {
if(node.name=="memory") {
@@ -104,7 +110,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val size = (node.args[1] as PtNumber).number.toUInt()
val align = (node.args[2] as PtNumber).number.toUInt()
// don't add memory slabs in nested scope, just put them in the top level of the ST
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
}
null
}
@@ -112,14 +118,14 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
}
if(stNode!=null) {
scope.peek().add(stNode)
scope.push(stNode)
scope.last().add(stNode)
scope.add(stNode)
}
node.children.forEach {
addToSt(it, scope)
}
if(stNode!=null)
scope.pop()
scope.removeLast()
}
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
@@ -130,7 +136,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name, null)
}
is PtIdentifier -> StArrayElement(null, it.name, null)
is PtNumber -> StArrayElement(it.number, null, null)
is PtBool -> StArrayElement(null, null, it.value)
else -> throw AssemblyError("invalid array element $it")
+10 -9
View File
@@ -80,18 +80,11 @@ class PtBlock(name: String,
val options: Options,
position: Position
) : PtNamedNode(name, position), IPtStatementContainer {
enum class BlockAlignment {
NONE,
WORD,
PAGE
}
class Options(val address: UInt? = null,
val forceOutput: Boolean = false,
val noSymbolPrefixing: Boolean = false,
val veraFxMuls: Boolean = false,
val ignoreUnused: Boolean = false,
val alignment: BlockAlignment = BlockAlignment.NONE)
val ignoreUnused: Boolean = false)
}
@@ -103,12 +96,20 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
companion object {
// all automatically generated labels everywhere need to have the same label name prefix:
const val GeneratedLabelPrefix = "p8_label_gen_"
}
}
class PtBreakpoint(position: Position): PtNode(position)
class PtAlign(val align: UInt, position: Position): PtNode(position)
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
+48 -20
View File
@@ -1,7 +1,7 @@
package prog8.code.ast
import prog8.code.core.*
import java.util.*
import java.util.Objects
import kotlin.math.abs
import kotlin.math.truncate
@@ -42,7 +42,18 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
else
other.left isSameAs left && other.right isSameAs right
}
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtContainmentCheck -> {
if(other !is PtContainmentCheck || other.type != type || !(other.needle isSameAs needle))
false
else {
if(haystackHeapVar!=null)
other.haystackHeapVar!=null && other.haystackHeapVar!! isSameAs haystackHeapVar!!
else if(haystackValues!=null)
other.haystackValues!=null && other.haystackValues!! isSameAs haystackValues!!
else
false
}
}
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtIrRegister -> other is PtIrRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
@@ -71,10 +82,11 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
}
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt() ?: (this as? PtBool)?.asInt()
fun asConstValue(): Double? = (this as? PtNumber)?.number ?: (this as? PtBool)?.asInt()?.toDouble()
fun isSimple(): Boolean {
return when(this) {
is PtAddressOf -> true
is PtAddressOf -> this.arrayIndexExpr==null || this.arrayIndexExpr?.isSimple()==true
is PtArray -> true
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
is PtBinaryExpression -> false
@@ -95,6 +107,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtRange -> true
is PtString -> true
is PtTypeCast -> value.isSimple()
is PtIfExpression -> condition.isSimple() && truevalue.isSimple() && falsevalue.isSimple()
}
}
@@ -152,6 +165,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
// children are always one of 3 types: PtBool, PtNumber or PtAddressOf.
override fun hashCode(): Int = Objects.hash(children, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtArray)
@@ -194,11 +208,28 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
}
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val element: PtExpression
class PtIfExpression(type: DataType, position: Position): PtExpression(type, position) {
val condition: PtExpression
get() = children[0] as PtExpression
val iterable: PtIdentifier
get() = children[1] as PtIdentifier
val truevalue: PtExpression
get() = children[1] as PtExpression
val falsevalue: PtExpression
get() = children[2] as PtExpression
}
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val needle: PtExpression
get() = children[0] as PtExpression
val haystackHeapVar: PtIdentifier?
get() = children[1] as? PtIdentifier
val haystackValues: PtArray?
get() = children[1] as? PtArray
companion object {
val MAX_SIZE_FOR_INLINE_CHECKS_BYTE = 5
val MAX_SIZE_FOR_INLINE_CHECKS_WORD = 4
}
}
@@ -208,6 +239,13 @@ class PtFunctionCall(val name: String,
position: Position) : PtExpression(type, position) {
val args: List<PtExpression>
get() = children.map { it as PtExpression }
init {
if(void) require(type==DataType.UNDEFINED) {
"void fcall should have undefined datatype"
}
// note: non-void calls can have UNDEFINED type: is if they return more than 1 value
}
}
@@ -227,12 +265,6 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOOL, position) {
companion object {
fun fromNumber(number: Number, position: Position): PtBool =
PtBool(number != 0.0, position)
}
override fun hashCode(): Int = Objects.hash(type, value)
override fun equals(other: Any?): Boolean {
@@ -302,6 +334,7 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal == toVal -> fromVal .. toVal
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
@@ -317,12 +350,11 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
val fromLv = from as? PtNumber
val toLv = to as? PtNumber
val stepLv = step as? PtNumber
if(fromLv==null || toLv==null || stepLv==null)
if(fromLv==null || toLv==null)
return null
val fromVal = fromLv.number.toInt()
val toVal = toLv.number.toInt()
val stepVal = stepLv.number.toInt()
val stepVal = step.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
}
@@ -346,7 +378,3 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else if(expr is PtBool) expr.asInt() else null
+29 -7
View File
@@ -10,6 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAlign -> "%align ${node.align}"
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
@@ -21,7 +22,17 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
"&"
}
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
is PtArray -> {
val valuelist = node.children.map {
when (it) {
is PtBool -> it.toString()
is PtNumber -> it.number.toString()
is PtIdentifier -> it.name
else -> "?"
}
}.joinToString(", ")
"array len=${node.children.size} ${type(node.type)} [ $valuelist ]"
}
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
@@ -78,16 +89,18 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}"
}
val str = if (node.inline) "inline " else ""
if(node.address==null) {
if(node.address == null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
} else {
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
val bank = if(node.address.constbank!=null) "@bank ${node.address.constbank}"
else if(node.address.varbank!=null) "@bank ${node.address.varbank?.name}"
else ""
str + "extsub $bank ${node.address.address.toHex()} = ${node.name}($params) $clobbers $returns"
}
}
is PtBlock -> {
val addr = if(node.options.address==null) "" else "@${node.options.address.toHex()}"
val align = if(node.options.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.options.alignment}"
"\nblock '${node.name}' $addr $align"
"\nblock '${node.name}' $addr"
}
is PtConstant -> {
val value = when(node.type) {
@@ -116,13 +129,20 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
is PtVariable -> {
val split = if(node.type in SplitWordArrayTypes) "@split" else ""
val align = when(node.align) {
0u -> ""
2u -> "@alignword"
64u -> "@align64"
256u -> "@alignpage"
else -> throw IllegalArgumentException("invalid alignment size")
}
val str = if(node.arraySize!=null) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[${node.arraySize}] $split ${node.name}"
"${eltType.name.lowercase()}[${node.arraySize}] $split $align ${node.name}"
}
else if(node.type in ArrayDatatypes) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[] $split ${node.name}"
"${eltType.name.lowercase()}[] $split $align ${node.name}"
}
else
"${node.type.name.lowercase()} ${node.name}"
@@ -144,6 +164,8 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
"->"
}
is PtDefer -> "<defer>"
is PtIfExpression -> "<ifexpr>"
}
}
+20 -4
View File
@@ -5,17 +5,21 @@ import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
val scopedName: String
}
class PtAsmSub(
name: String,
val address: UInt?,
val address: Address?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position), IPtSubroutine
) : PtNamedNode(name, position), IPtSubroutine {
class Address(val constbank: UByte?, var varbank: PtIdentifier?, val address: UInt)
}
class PtSub(
@@ -130,7 +134,8 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
class PtReturn(position: Position) : PtNode(position) {
val hasValue = children.any()
val hasValue: Boolean
get() = children.any()
val value: PtExpression?
get() {
return if(children.any())
@@ -147,7 +152,15 @@ sealed interface IPtVariable {
}
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
class PtVariable(
name: String,
override val type: DataType,
val zeropage: ZeropageWish,
val align: UInt,
val value: PtExpression?,
val arraySize: UInt?,
position: Position
) : PtNamedNode(name, position), IPtVariable {
init {
value?.let {it.parent=this}
}
@@ -178,3 +191,6 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
}
class PtDefer(position: Position): PtNode(position), IPtStatementContainer
+16
View File
@@ -0,0 +1,16 @@
package prog8.code.ast
import prog8.code.SymbolTable
import prog8.code.core.*
fun verifyFinalAstBeforeAsmGen(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
/*
walkAst(program) { node, _ ->
if(node is PtVariable) {
if(node.value!=null) {
// require(node.type in ArrayDatatypes || node.type==DataType.STR) { "final check before asmgen: only string and array variables can still have an init value ${node.name} ${node.type} ${node.position}"}
}
}
}
*/
}
@@ -49,11 +49,14 @@ class FSignature(val pure: Boolean, // does it have side effects?
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter goes via register/registerpair
// One parameter goes via register/registerpair.
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
// TODO can we start using the X register as well for a third byte or word+byte combo
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // TODO is this correct? shouldn't it be FAC1?
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
@@ -79,7 +82,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
@@ -128,6 +130,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"rrestore" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar2" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("argA", arrayOf(DataType.UBYTE)), FParam("argX", arrayOf(DataType.UBYTE)), FParam("argY", arrayOf(DataType.UBYTE)), FParam("argC", arrayOf(DataType.BOOL))), DataType.UWORD),
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
)
@@ -14,6 +14,7 @@ class CompilationOptions(val output: OutputType,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var memtopAddress: UInt,
var warnSymbolShadowing: Boolean = false,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
@@ -27,7 +28,7 @@ class CompilationOptions(val output: OutputType,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var splitWordArrays: Boolean = false,
var strictBool: Boolean = true,
var addMissingRts: Boolean = false, // deprecated, will likely go way in future version
var breakpointCpuInstruction: String? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
@@ -13,7 +13,9 @@ interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val BSSGOLDENRAM_START: UInt
+67 -145
View File
@@ -1,6 +1,6 @@
package prog8.code.optimize
import prog8.code.StRomSub
import prog8.code.StExtSub
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
@@ -10,9 +10,8 @@ fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st:
if (!options.optimize)
return
while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
(optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling
}
@@ -28,97 +27,6 @@ private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
}
private var tempVarCounter = 0
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
if(expr is PtArrayIndexer && expr.index.isSimple())
return false
if (expr is PtMemoryByte && expr.address.isSimple())
return false
val result = if(expr is PtBinaryExpression)
expr.type !in ByteDatatypes ||
!(expr.left.isSimple() && expr.right.isSimple()) ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
true
else
!expr.isSimple()
return result
}
// for each Binaryexpression, recurse to find a common subexpression pair therein.
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtBinaryExpression) {
val subExpressions = mutableListOf<PtExpression>()
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
if (subNode is PtExpression) {
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
true
} else false
}
outer@for (first in subExpressions) {
for (second in subExpressions) {
if (first!==second && first isSameAs second) {
commons[node] = first to second
break@outer // do only 1 replacement at a time per binaryexpression
}
}
}
false
} else true
}
// replace common subexpressions by a temp variable that is assigned only once.
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
commons.forEach { binexpr, (occurrence1, occurrence2) ->
val (stmtContainer, stmt) = findContainingStatements(binexpr)
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
val containerScopedName = findScopeName(stmtContainer)
tempVarCounter++
val tempvarName = "prog8_subexprvar_$tempVarCounter"
// TODO: some tempvars could be reused, if they are from different lines
val datatype = occurrence1.type
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
occurrence1.parent.children[occurrence1idx] = singleReplacement1
singleReplacement1.parent = occurrence1.parent
occurrence2.parent.children[occurrence2idx] = singleReplacement2
singleReplacement2.parent = occurrence2.parent
val tempassign = PtAssignment(binexpr.position).also { assign ->
assign.add(PtAssignTarget(false, binexpr.position).also { tgt->
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
})
assign.add(occurrence1)
occurrence1.parent = assign
}
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
tempassign.parent = stmtContainer
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position)
stmtContainer.add(0, tempvar)
tempvar.parent = stmtContainer
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
}
return commons.size
}
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
var changes = 0
walkAst(program) { node: PtNode, depth: Int ->
@@ -131,7 +39,7 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
}
if(functionName!=null) {
val stNode = st.lookup(functionName)
if (stNode is StRomSub) {
if (stNode is StExtSub) {
require(node.children.size==stNode.returns.size+1) {
"number of targets must match return values"
}
@@ -152,7 +60,7 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
val index = node.parent.children.indexOf(node)
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
val voidCall = PtFunctionCall(functionName, true, DataType.UNDEFINED, value.position)
value.children.forEach { voidCall.add(it) }
node.parent.children[index] = voidCall
voidCall.parent = node.parent
@@ -170,38 +78,73 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
return 0 // the special bittest optimization is not yet valid for the IR
fun makeBittestCall(condition: PtBinaryExpression, and: PtBinaryExpression, variable: PtIdentifier, bitmask: Int): PtBuiltinFunctionCall {
require(bitmask==128 || bitmask==64)
val setOrNot = if(condition.operator=="!=") "set" else "notset"
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, condition.position)
bittestCall.add(variable)
if(bitmask==128)
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
else
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
return bittestCall
}
fun isAndByteCondition(condition: PtBinaryExpression?): Triple<PtBinaryExpression, PtIdentifier, Int>? {
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
if (condition.right.asConstInteger() == 0) {
val and = condition.left as? PtBinaryExpression
if (and != null && and.operator == "&" && and.type == DataType.UBYTE) {
val bitmask = and.right.asConstInteger()
if(bitmask==128 || bitmask==64) {
val variable = and.left as? PtIdentifier
if (variable != null && variable.type in ByteDatatypes) {
return Triple(and, variable, bitmask)
}
val typecast = and.left as? PtTypeCast
if (typecast != null && typecast.type == DataType.UBYTE) {
val castedVariable = typecast.value as? PtIdentifier
if(castedVariable!=null && castedVariable.type in ByteDatatypes)
return Triple(and, castedVariable, bitmask)
}
}
}
}
}
return null
}
var changes = 0
var recurse = true
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtIfElse) {
val condition = node.condition as? PtBinaryExpression
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
if(condition.right.asConstInteger()==0) {
val and = condition.left as? PtBinaryExpression
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
val variable = and.left as? PtIdentifier
val bitmask = and.right.asConstInteger()
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
val setOrNot = if(condition.operator=="!=") "set" else "notset"
val index = node.parent.children.indexOf(node)
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
bittestCall.add(variable)
if(bitmask==128)
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
else
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
val ifElse = PtIfElse(node.position)
ifElse.add(bittestCall)
ifElse.add(node.ifScope)
if(node.hasElse())
ifElse.add(node.elseScope)
node.parent.children[index] = ifElse
ifElse.parent = node.parent
changes++
recurse = false
}
}
}
val check = isAndByteCondition(condition)
if(check!=null) {
val (and, variable, bitmask) = check
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
val ifElse = PtIfElse(node.position)
ifElse.add(bittestCall)
ifElse.add(node.ifScope)
if (node.hasElse())
ifElse.add(node.elseScope)
val index = node.parent.children.indexOf(node)
node.parent.children[index] = ifElse
ifElse.parent = node.parent
changes++
recurse = false
}
}
if (node is PtIfExpression) {
val condition = node.condition as? PtBinaryExpression
val check = isAndByteCondition(condition)
if(check!=null) {
val (and, variable, bitmask) = check
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
node.children[0] = bittestCall
bittestCall.parent = node
changes++
recurse = false
}
}
recurse
@@ -235,24 +178,3 @@ internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister:
}
return false // there are no identifiers directly corresponding to cpu registers
}
internal fun findScopeName(node: PtNode): String {
var parent=node
while(parent !is PtNamedNode)
parent = parent.parent
return parent.scopedName
}
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
var parent = node.parent
var child = node
while(true) {
if(parent is IPtStatementContainer) {
return parent to child
}
child=parent
parent=parent.parent
}
}
+3 -1
View File
@@ -27,6 +27,7 @@ val CompilationTargets = listOf(
Cx16Target.NAME,
PETTarget.NAME,
AtariTarget.NAME,
Neo6502Target.NAME,
VMTarget.NAME
)
@@ -37,5 +38,6 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
Neo6502Target.NAME -> Neo6502Target()
else -> throw IllegalArgumentException("invalid compilation target")
}
}
@@ -0,0 +1,30 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.neo6502.Neo6502MachineDefinition
class Neo6502Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = Neo6502MachineDefinition()
override val defaultEncoding = Encoding.ISO
companion object {
const val NAME = "neo"
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypesWithBoolean -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> throw IllegalArgumentException("invalid datatype")
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
if(arrayDt== DataType.UWORD)
numElements // pointer to bytes.
else
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}
@@ -11,7 +11,9 @@ class AtariMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x2000u
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // TODO what's memtop?
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
@@ -13,7 +13,9 @@ class C128MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xff00u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
@@ -14,7 +14,10 @@ class C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = 0xcfffu
@@ -13,7 +13,9 @@ class CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
@@ -0,0 +1,50 @@
package prog8.code.target.neo6502
import prog8.code.core.*
import java.nio.file.Path
class Neo6502MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0800u
override val PROGRAM_MEMTOP_ADDRESS = 0xfc00u // kernal starts here
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 lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The neo target only supports the main emulator (neo).")
return
}
val cmdline = listOf("neo", "${programNameWithPath}.bin@800", "cold")
println("\nStarting Neo6502 emulator...")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xff00u..0xff0fu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = Neo6502Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
@@ -0,0 +1,48 @@
package prog8.code.target.neo6502
import prog8.code.core.*
class Neo6502Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xfau // temp storage for a single byte
override val SCRATCH_REG = 0xfbu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfcu // temp storage 1 for a word $fc+$fd
override val SCRATCH_W2 = 0xfeu // temp storage 2 for a word $fe+$ff
init {
if (options.floats) {
throw InternalCompilerException("Neo6502 target doesn't support floating point routines")
}
when (options.zeropage) {
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> {
free.addAll(0x22u..0xffu)
}
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}
@@ -13,7 +13,9 @@ class PETMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
override val BSSHIGHRAM_START = 0u
override val BSSHIGHRAM_END = 0u
@@ -13,7 +13,9 @@ class VirtualMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 8 // 64-bits double
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
+1
View File
@@ -28,6 +28,7 @@ dependencies {
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
+1
View File
@@ -14,5 +14,6 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
@@ -9,7 +9,6 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.assignment.*
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.writeLines
@@ -17,15 +16,15 @@ import kotlin.io.path.writeLines
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSequenceNr: Int): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
errors: IErrorReporter,
): IAssemblyProgram? {
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
val asmgen = AsmGen6502Internal(program, st, options, errors)
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr)
return asmgen.compileToAssembly()
}
@@ -37,10 +36,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
when(node) {
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
is PtBlock -> node.name = "p8b_${node.name}"
is PtLabel -> node.name = "p8l_${node.name}"
is PtLabel -> if(!node.name.startsWith(PtLabel.GeneratedLabelPrefix)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
else -> node.name = "p8_${node.name}"
}
}
@@ -49,6 +47,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtAsmSub -> {
prefixNamedNode(node)
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
if(node.address?.varbank!=null) {
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
}
}
is PtSub -> {
prefixNamedNode(node)
@@ -104,7 +105,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
is PtVariable -> parent.children[index] = node.prefix(st)
is PtVariable -> parent.children[index] = node.prefix(parent, st)
else -> throw AssemblyError("weird node to prefix $node")
}
}
@@ -124,18 +125,22 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
private fun prefixScopedName(name: String, type: Char): String {
if('.' !in name)
if('.' !in name) {
if(name.startsWith(PtLabel.GeneratedLabelPrefix))
return name
return "p8${type}_$name"
}
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPrefixed = "p8${type}_${parts.last()}"
val lastPart = parts.last()
val lastPrefixed = if(lastPart.startsWith(PtLabel.GeneratedLabelPrefix)) lastPart else "p8${type}_$lastPart"
// the parts in between are assumed to be subroutine scopes.
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
return prefixed.joinToString(".")
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
name = prefixScopedName(name, 'v')
if(value==null)
return this
@@ -145,7 +150,6 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
val newValue = PtArray(arrayValue.type, arrayValue.position)
arrayValue.children.forEach { elt ->
when(elt) {
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
is PtBool -> newValue.add(elt)
is PtNumber -> newValue.add(elt)
is PtAddressOf -> {
@@ -163,7 +167,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
PtVariable(name, type, zeropage, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, align, newValue, arraySize, position)
result.parent = parent
result
}
else this
}
@@ -199,7 +205,7 @@ private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
val prefixType = when(target!!.type) {
StNodeType.BLOCK -> 'b'
StNodeType.SUBROUTINE, StNodeType.ROMSUB -> 's'
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
StNodeType.LABEL -> 'l'
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
StNodeType.CONSTANT -> 'c'
@@ -218,7 +224,8 @@ class AsmGen6502Internal (
val program: PtProgram,
internal val symbolTable: SymbolTable,
internal val options: CompilationOptions,
internal val errors: IErrorReporter
internal val errors: IErrorReporter,
private var generatedLabelSequenceNumber: Int
) {
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
@@ -232,7 +239,7 @@ class AsmGen6502Internal (
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, options, anyExprGen, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen, errors)
@@ -374,7 +381,7 @@ class AsmGen6502Internal (
val name = asmSymbolName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length + 1)
else
@@ -385,14 +392,14 @@ class AsmGen6502Internal (
val name = asmVariableName(identifier.name)
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
val subName = identifier.definingSub()?.scopedName
val subName = identifier.definingISub()?.scopedName
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
name.drop(subName.length+1)
else
name
}
fun asmVariableName(st: StNode, scope: PtSub?): String {
fun asmVariableName(st: StNode, scope: IPtSubroutine?): String {
val name = asmVariableName(st.scopedName)
if(scope==null)
return name
@@ -580,6 +587,7 @@ class AsmGen6502Internal (
internal fun translate(stmt: PtNode) {
outputSourceLine(stmt)
when(stmt) {
is PtAlign -> if(stmt.align > 1u) out(" .align ${stmt.align.toHex()}")
is PtReturn -> translate(stmt)
is PtSub -> programGen.translateSubroutine(stmt)
is PtAsmSub -> programGen.translateAsmSubroutine(stmt)
@@ -602,9 +610,10 @@ class AsmGen6502Internal (
is PtRepeatLoop -> translate(stmt)
is PtWhen -> translate(stmt)
is PtIncludeBinary -> translate(stmt)
is PtBreakpoint -> translate(stmt)
is PtBreakpoint -> translateBrk()
is PtVariable, is PtConstant, is PtMemMapped -> { /* do nothing; variables are handled elsewhere */ }
is PtBlock -> throw AssemblyError("block should have been handled elsewhere")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtNodeGroup -> stmt.children.forEach { translate(it) }
is PtNop -> {}
else -> throw AssemblyError("missing asm translation for $stmt")
@@ -772,7 +781,7 @@ class AsmGen6502Internal (
private fun translate(stmt: PtRepeatLoop) {
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopEndLabels.add(endLabel)
when (stmt.count) {
is PtNumber -> {
@@ -780,7 +789,7 @@ class AsmGen6502Internal (
when {
iterations == 0 -> {}
iterations == 1 -> translate(stmt.statements)
iterations<0 || iterations>65535 -> throw AssemblyError("invalid number of iterations")
iterations<0 || iterations>65536 -> throw AssemblyError("invalid number of iterations")
iterations <= 256 -> repeatByteCount(iterations, stmt)
else -> repeatWordCount(iterations, stmt)
}
@@ -816,14 +825,14 @@ class AsmGen6502Internal (
}
}
loopEndLabels.pop()
loopEndLabels.removeLast()
}
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65535) { "invalid repeat count ${stmt.position}" }
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
val loopcount = if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$loopcount
lda #<$loopcount
@@ -1038,7 +1047,7 @@ $repeatLabel""")
}
}
private fun translate(ret: PtReturn, withRts: Boolean=true) {
private fun translate(ret: PtReturn) {
ret.value?.let { returnvalue ->
val sub = ret.definingSub()!!
val returnReg = sub.returnRegister()!!
@@ -1055,9 +1064,7 @@ $repeatLabel""")
}
}
}
if(withRts)
out(" rts")
out(" rts")
}
private fun translate(asm: PtInlineAssembly) {
@@ -1082,7 +1089,7 @@ $repeatLabel""")
out(" .binary \"$pathForAssembler\" $offset $length")
}
private fun translate(brk: PtBreakpoint) {
private fun translateBrk() {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out(label)
@@ -1361,11 +1368,9 @@ $repeatLabel""")
extra
}
private var generatedLabelSequenceNumber: Int = 0
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "label_asm_${generatedLabelSequenceNumber}_$postfix"
return "${PtLabel.GeneratedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
}
fun assignConstFloatToPointerAY(number: PtNumber) {
@@ -1379,6 +1384,10 @@ $repeatLabel""")
pla
jsr floats.copy_float""")
}
internal fun assignIfExpression(target: AsmAssignTarget, value: PtIfExpression) {
ifElseAsmgen.assignIfExpression(target, value)
}
}
/**
@@ -3,6 +3,7 @@ package prog8.codegen.cpu6502
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.ast.PtLabel
import prog8.code.core.IMachineDefinition
@@ -361,15 +362,16 @@ or *_afterif labels.
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
*/
val autoLabelPrefix = PtLabel.GeneratedLabelPrefix
if(first=="beq +" && second=="lda #1" && third=="+") {
if((fourth.startsWith("beq label_") || fourth.startsWith("bne label_")) &&
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
else if(fourth.startsWith("label_") && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) {
if((fifth.startsWith("beq label_") || fifth.startsWith("bne label_")) &&
else if(fourth.startsWith(autoLabelPrefix) && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) {
if((fifth.startsWith("beq $autoLabelPrefix") || fifth.startsWith("bne $autoLabelPrefix")) &&
(fifth.endsWith("_shortcut") || fifth.endsWith("_afterif") || fifth.endsWith("_shortcut:") || fifth.endsWith("_afterif:"))) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
@@ -516,34 +518,37 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
val first = lines[0].value
val second = lines[1].value
val third = lines[2].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
if (!haslabel(second)) {
if(!haslabel(second)) {
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
if("floats.pushFAC" !in first && "floats.popFAC" !in first) { // these 2 routines depend on being called with JSR!!
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
}
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
if ((" lda" in first || "\tlda" in first) && (" cmp #0" in second || "\tcmp #0" in second) ||
(" ldx" in first || "\tldx" in first) && (" cpx #0" in second || "\tcpx #0" in second) ||
(" ldy" in first || "\tldy" in first) && (" cpy #0" in second || "\tcpy #0" in second)
@@ -11,18 +11,22 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (order Y,X,A),
// 4) CPU Carry status flag
// 4) floating point registers (FAC1, FAC2),
// 5) CPU Carry status flag
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
floatRegs.forEach { order += it.index }
rest.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
@@ -1,13 +1,16 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtLabel
import prog8.code.core.*
import prog8.code.target.AtariTarget
import prog8.code.target.C64Target
import prog8.code.target.Neo6502Target
import java.nio.file.Path
internal class AssemblyProgram(
override val name: String,
outputDir: Path,
private val outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
@@ -16,6 +19,7 @@ internal class AssemblyProgram(
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
private val listFile = outputDir.resolve("$name.list")
private val targetWithoutBreakpointsForEmulator = setOf(AtariTarget.NAME, Neo6502Target.NAME)
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
@@ -96,12 +100,47 @@ internal class AssemblyProgram(
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
"neo" -> {
// Neo6502 raw program generation.
if(options.output!=OutputType.RAW || options.loadAddress!=0x0800u || options.launcher!=CbmPrgLauncherType.NONE) {
throw AssemblyError("invalid program compilation options. Neo6502 requires %output raw, %launcher none, %address $0800")
}
// TODO are these options okay for neo?
val command = mutableListOf("64tass", "--case-sensitive", "--long-branch",
"-Wall", // "-Werror", "-Wno-strict-bool"
"--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type, need 'raw'")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
else -> throw AssemblyError("invalid compilation target")
}
val proc = ProcessBuilder(assemblerCommand).inheritIO().start()
val result = proc.waitFor()
if (result == 0 && compTarget.name!="atari") {
if (result == 0 && compTarget.name !in targetWithoutBreakpointsForEmulator) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
@@ -109,7 +148,7 @@ internal class AssemblyProgram(
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
val pattern = Regex("""al (\w+) \S+${PtLabel.GeneratedLabelPrefix}.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
@@ -1,7 +1,5 @@
package prog8.codegen.cpu6502
import prog8.code.StMemVar
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -64,120 +62,32 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"callfar2" -> funcCallFar2(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
"prog8_lib_arraycopy" -> funcArrayCopy(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcArrayCopy(fcall: PtBuiltinFunctionCall) {
val source = fcall.args[0] as PtIdentifier
val target = fcall.args[1] as PtIdentifier
val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) {
is StStaticVariable -> sourceSymbol.length!!
is StMemVar -> sourceSymbol.length!!
else -> 0
}
val sourceAsm = asmgen.asmVariableName(source)
val targetAsm = asmgen.asmVariableName(target)
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
// split -> split words (copy lsb and msb arrays separately)
asmgen.out("""
lda #<${sourceAsm}_lsb
ldy #>${sourceAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_lsb
ldy #>${targetAsm}_lsb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numElements and 255}
jsr prog8_lib.memcopy_small
lda #<${sourceAsm}_msb
ldy #>${sourceAsm}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_msb
ldy #>${targetAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numElements and 255}
jsr prog8_lib.memcopy_small""")
}
else if(source.type in SplitWordArrayTypes) {
// split word array to normal word array (copy lsb and msb arrays separately)
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
asmgen.out("""
lda #<${sourceAsm}_lsb
ldy #>${sourceAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${sourceAsm}_msb
ldy #>${sourceAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda #<${targetAsm}
ldy #>${targetAsm}
ldx #${numElements and 255}
jsr prog8_lib.arraycopy_split_to_normal_words""")
}
else if(target.type in SplitWordArrayTypes) {
// normal word array to split array
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
asmgen.out("""
lda #<${targetAsm}_lsb
ldy #>${targetAsm}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}_msb
ldy #>${targetAsm}_msb
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda #<${sourceAsm}
ldy #>${sourceAsm}
ldx #${numElements and 255}
jsr prog8_lib.arraycopy_normal_to_split_words""")
}
else {
// normal array to array copy, various element types
val eltsize = asmgen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
val numBytes = numElements * eltsize
asmgen.out("""
lda #<${sourceAsm}
ldy #>${sourceAsm}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${targetAsm}
ldy #>${targetAsm}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #${numBytes and 255}
jsr prog8_lib.memcopy_small""")
}
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {
DataType.UBYTE -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
asmgen.out(" tay | jsr math.multiply_bytes")
asmgen.out(" tay | jsr prog8_math.multiply_bytes")
if(resultRegister!=null) {
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
}
}
DataType.UWORD -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr math.square")
asmgen.out(" jsr prog8_math.square")
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
@@ -194,7 +104,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A ,false)
// math.divmod_ub_asm: -- divide A by Y, result quotient in Y, remainder in A (unsigned)
asmgen.out(" jsr math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
@@ -208,7 +118,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
// input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
// output: P8ZP_SCRATCH_W2 in ZP: 16 bit remainder, A/Y: 16 bit division result
asmgen.out(" jsr math.divmod_uw_asm")
asmgen.out(" jsr prog8_math.divmod_uw_asm")
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
val remainderVar = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
@@ -266,33 +176,124 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcCall(fcall: PtBuiltinFunctionCall) {
// note: the routine can return a word value (in AY)
val constAddr = fcall.args[0].asConstInteger()
if(constAddr!=null) {
asmgen.out(" jsr ${constAddr.toHex()}")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address
asmgen.out("""
sta (+)+1
sty (+)+2
+ jsr 0 ; modified""")
return
}
// note: the routine can return a word value (in AY)
val identifier = fcall.args[0] as? PtIdentifier
if(identifier!=null) {
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
pha
lda #<((+)-1)
pha
jmp (${asmgen.asmSymbolName(identifier)})
+""")
return
}
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
pha
lda #<((+)-1)
pha
jmp (P8ZP_SCRATCH_W2)
+""")
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
val targetName = asmgen.options.compTarget.name
if(targetName !in arrayOf("cx16", "c64", "c128"))
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
val jsrfar = when(targetName) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
else -> TODO("jsrfar routine")
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr $jsrfar
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr $jsrfar
+ .word 0
+ .byte 0""")
}
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
private fun funcCallFar2(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val targetName = asmgen.options.compTarget.name
if(targetName !in arrayOf("cx16", "c64", "c128"))
throw AssemblyError("callfar2 only works on cx16, c64 and c128 targets at this time")
fun assignArgs() {
fun assign(value: PtExpression, register: Char) {
when(value) {
is PtBool -> asmgen.out(" ld$register #${value.asInt()}")
is PtNumber -> asmgen.out(" ld$register #${value.number.toInt()}")
is PtIdentifier -> asmgen.out(" ld$register ${asmgen.asmVariableName(value)}")
else -> TODO("callfar2: support non-simple expressions for arguments")
}
}
assign(fcall.args[2], 'a')
assign(fcall.args[3], 'x')
assign(fcall.args[4], 'y')
val carry = fcall.args[5].asConstInteger()
if(carry!=null)
asmgen.out(if(carry==0) " clc" else " sec")
else
TODO("callfar2: support non-const argument values")
}
val jsrfar = when(targetName) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
else -> TODO("jsrfar routine")
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
assignArgs()
asmgen.out("""
jsr $jsrfar
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
assignArgs()
asmgen.out("""
jsr $jsrfar
+ .word 0
+ .byte 0""")
}
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
@@ -1122,7 +1123,44 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
when(resultRegister) {
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the msb byte out of the word array
if(arg.splitWords) {
val arrayVar = asmgen.asmVariableName(arg.variable)+"_msb"
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" ldy $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" ldx $arrayVar,y")
}
else -> throw AssemblyError("invalid reg")
}
} else {
val arrayVar = asmgen.asmVariableName(arg.variable)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar+1,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" ldy $arrayVar+1,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" ldx $arrayVar+1,y")
}
else -> throw AssemblyError("invalid reg")
}
}
} else when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
@@ -1137,6 +1175,23 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" pla")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya | ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" txa | ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
asmgen.out(" tya | tax | ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
val reg = "cx16.${resultRegister.toString().lowercase()}"
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" sty ${reg}L | lda #0 | sta ${reg}H | lda ${reg}L")
}
else -> throw AssemblyError("invalid reg")
}
}
@@ -1178,7 +1233,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" lda $arrayVar,x")
asmgen.out(" ldy $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@@ -1216,33 +1271,15 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(arg, resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
asmgen.out(" lda cx16.r0L")
val reg = "cx16.${resultRegister.toString().lowercase()}"
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" sta ${reg}L | ldy #0 | sty ${reg}H | cmp #0")
}
else -> throw AssemblyError("invalid reg")
}
}
}
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
// address goes in P8ZP_SCRATCH_W1, number of elements in A
val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
}
val identifierName = asmgen.asmVariableName(arg)
asmgen.out("""
lda #<$identifierName
ldy #>$identifierName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements and 255}""")
}
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map { it.type})
@@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
@@ -48,25 +48,43 @@ internal class ForLoopsAsmGen(
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
if (stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
bne $loopLabel""")
}
else if (stepsize==1 || stepsize==-1) {
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
}
else
asmgen.out("""
sec
@@ -75,12 +93,13 @@ internal class ForLoopsAsmGen(
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0)
if(stepsize<0) {
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
}
else
asmgen.out(" cmp $varname | bcc $endLabel")
asmgen.out(" sta $modifiedLabel+1")
@@ -100,8 +119,6 @@ $modifiedLabel cmp #0 ; modified
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt==DataType.ARRAY_B) {
@@ -155,58 +172,85 @@ $modifiedLabel cmp #0 ; modified
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
if(stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #1
bne +
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize == 1 || stepsize == -1) {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
asmgen.jmp(loopLabel)
}
stepsize > 0 -> {
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
asmgen.out(endLabel)
}
else if (stepsize > 0) {
// (u)words, step >= 2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -222,8 +266,8 @@ $modifiedLabel2 lda #0 ; modified
bcc $endLabel
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -239,22 +283,20 @@ $modifiedLabel lda #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else -> {
}
else {
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
// (u)words, step <= -2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
@@ -271,13 +313,12 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -333,7 +374,7 @@ $endLabel""")
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
@@ -477,17 +518,19 @@ $loopLabel sty $indexVar
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
if(range.last==range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range, iterableDt==DataType.ARRAY_UB)
}
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
if(range.last==range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
@@ -495,7 +538,7 @@ $loopLabel sty $indexVar
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2
@@ -507,7 +550,7 @@ $loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
}
2 -> {
if(range.last==255 || range.last==254) {
@@ -601,13 +644,13 @@ $loopLabel""")
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
@@ -627,50 +670,50 @@ $endLabel""")
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression, unsigned: Boolean) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
if(!unsigned || range.first<=127) {
asmgen.out("""
dec $varname
bpl $loopLabel""")
} else {
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
}
1 -> {
asmgen.out("""
dec $varname
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
else -> {
asmgen.out("""
dec $varname
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
bne $loopLabel""")
}
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -691,13 +734,13 @@ $loopLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
@@ -706,20 +749,29 @@ $loopLabel""")
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel""")
}
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
asmgen.loopEndLabels.removeLast()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
@@ -15,6 +15,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// just ignore any result values from the function call.
}
// TODO tweak subroutine call convention to also make use of X register to pass a third byte?
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypesWithBoolean)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean)
@@ -40,7 +41,84 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
asmgen.out(" jsr $subAsmName")
val bank = sub.address?.constbank?.toString()
if(bank==null) {
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
if(varbank!=null) {
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
"c64" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
"c128" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
}
} else {
asmgen.out(" jsr $subAsmName")
}
}
else {
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}
"c64" -> {
asmgen.out("""
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}
"c128" -> {
asmgen.out("""
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
}
}
else if(sub is PtSub) {
@@ -82,7 +160,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtAddressOf -> false
is PtIdentifier -> false
is PtIrRegister -> false
is PtMemoryByte -> true // TODO might not actually need extra registers if the value has to end up in A
is PtMemoryByte -> arg.address !is PtNumber && arg.address !is PtIdentifier
is PtNumber -> false
is PtBool -> false
else -> true
@@ -205,7 +283,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
addr.add(value)
addr.parent = sub as PtNode
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
@@ -213,7 +291,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, target.position), scope)
}
return RegisterOrStatusflag(register, null)
}
@@ -1,6 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.StRomSub
import prog8.code.StExtSub
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
@@ -17,7 +17,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
fun translate(stmt: PtIfElse) {
require(stmt.condition.type== DataType.BOOL)
checkNotRomsubReturnsStatusReg(stmt.condition)
checkNotExtsubReturnsStatusReg(stmt.condition)
val jumpAfterIf = stmt.ifScope.children.singleOrNull() as? PtJump
@@ -43,23 +43,105 @@ internal class IfElseAsmGen(private val program: PtProgram,
val prefixCond = stmt.condition as? PtPrefix
if(prefixCond?.operator=="not") {
checkNotRomsubReturnsStatusReg(prefixCond.value)
assignConditionValueToRegisterAndTest(prefixCond.value)
return if(jumpAfterIf!=null)
translateJumpElseBodies("beq", "bne", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
if(stmt.hasElse())
throw AssemblyError("not prefix in ifelse should have been replaced by swapped if-else blocks")
else {
checkNotExtsubReturnsStatusReg(prefixCond.value)
assignConditionValueToRegisterAndTest(prefixCond.value)
return if (jumpAfterIf != null)
translateJumpElseBodies("beq", "bne", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
}
private fun checkNotRomsubReturnsStatusReg(condition: PtExpression) {
internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) {
// this is NOT for the if-else STATEMENT, but this is code for the IF-EXPRESSION.
require(target.datatype==expr.type)
val falseLabel = asmgen.makeLabel("ifexpr_false")
val endLabel = asmgen.makeLabel("ifexpr_end")
evalConditonAndBranchWhenFalse(expr.condition, falseLabel)
when(expr.type) {
in ByteDatatypesWithBoolean -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
in WordDatatypes -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
asmgen.out(endLabel)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt")
}
}
private fun evalConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) {
if (condition is PtBinaryExpression) {
return when(condition.right.type) {
in ByteDatatypesWithBoolean -> translateIfByteConditionBranch(condition, falseLabel)
in WordDatatypes -> translateIfWordConditionBranch(condition, falseLabel)
DataType.FLOAT -> translateFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
}
else if(condition is PtPrefix && condition.operator=="not") {
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
} else {
// 'simple' condition, check if it is a byte bittest
val bittest = condition as? PtBuiltinFunctionCall
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
val variable = bittest.args[0] as PtIdentifier
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
val testForBitSet = bittest.name.endsWith("_set")
when (bitnumber) {
7 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bpl $falseLabel")
else asmgen.out(" bmi $falseLabel")
return
}
6 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bvc $falseLabel")
else asmgen.out(" bvs $falseLabel")
return
}
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
}
}
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
}
private fun checkNotExtsubReturnsStatusReg(condition: PtExpression) {
val fcall = condition as? PtFunctionCall
if(fcall!=null && fcall.type==DataType.BOOL) {
val romsub = st.lookup(fcall.name) as? StRomSub
if(romsub!=null && romsub.returns.any { it.register.statusflag!=null }) {
throw AssemblyError("if romsub() that returns a status register boolean should have been changed into a Conditional branch such as if_cc")
val extsub = st.lookup(fcall.name) as? StExtSub
if(extsub!=null && extsub.returns.any { it.register.statusflag!=null }) {
throw AssemblyError("if extsub() returning a status register boolean should have been changed into a Conditional branch such as if_cc")
}
}
}
@@ -73,6 +155,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
is PtIrRegister,
is PtArrayIndexer,
is PtPrefix,
is PtIfExpression,
is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ }
is PtTypeCast -> {
if(condition.value.type !in ByteDatatypes && condition.value.type !in WordDatatypes)
@@ -276,6 +359,230 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateIfByteConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
val signed = condition.left.type in SignedDatatypes
val constValue = condition.right.asConstInteger()
if(constValue==0) {
return translateIfCompareWithZeroByteBranch(condition, signed, falseLabel)
}
when(condition.operator) {
"==" -> {
// if X==value
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
cmpAwithByteValue(condition.right, false)
asmgen.out(" bne $falseLabel")
}
"!=" -> {
// if X!=value
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
cmpAwithByteValue(condition.right, false)
asmgen.out(" beq $falseLabel")
}
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, condition.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
asmgen.out(" beq $falseLabel")
} else {
errors.warn("SLOW FALLBACK FOR 'IFEXPR' CODEGEN - ask for support", condition.position) // should not occur ;-)
assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
}
else -> {
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
// TODO: special cases for <, <=, >, >= above.
assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
}
}
private fun translateIfWordConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
// TODO can we reuse this whole thing from IfElse ?
val constValue = condition.right.asConstInteger()
if(constValue!=null) {
if (constValue == 0) {
when (condition.operator) {
"==" -> return translateWordExprIsZero(condition.left, falseLabel)
"!=" -> return translateWordExprIsNotZero(condition.left, falseLabel)
}
}
if (constValue != 0) {
when (condition.operator) {
"==" -> return translateWordExprEqualsNumber(condition.left, constValue, falseLabel)
"!=" -> return translateWordExprNotEqualsNumber(condition.left, constValue, falseLabel)
}
}
}
val variable = condition.right as? PtIdentifier
if(variable!=null) {
when (condition.operator) {
"==" -> return translateWordExprEqualsVariable(condition.left, variable, falseLabel)
"!=" -> return translateWordExprNotEqualsVariable(condition.left, variable, falseLabel)
}
}
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
private fun translateWordExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if w!=variable
// TODO reuse code from ifElse?
val varRight = asmgen.asmVariableName(variable)
if(expr is PtIdentifier) {
val varLeft = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varLeft
cmp $varRight
bne +
lda $varLeft+1
cmp $varRight+1
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out("""
cmp $varRight
bne +
cpy $varRight+1
beq $falseLabel
+""")
}
}
private fun translateWordExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if w==variable
// TODO reuse code from ifElse?
val varRight = asmgen.asmVariableName(variable)
if(expr is PtIdentifier) {
val varLeft = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varLeft
cmp $varRight
bne $falseLabel
lda $varLeft+1
cmp $varRight+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out("""
cmp $varRight
bne $falseLabel
cpy $varRight+1
bne $falseLabel""")
}
}
private fun translateWordExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
// if w!=number
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp #<$number
bne +
lda $varname+1
cmp #>$number
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out("""
cmp #<$number
bne +
cpy #>$number
beq $falseLabel
+""")
}
}
private fun translateWordExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
// if w==number
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp #<$number
bne $falseLabel
lda $varname+1
cmp #>$number
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out( """
cmp #<$number
bne $falseLabel
cpy #>$number
bne $falseLabel""")
}
}
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if w!=0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
beq $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | beq $falseLabel")
}
}
private fun translateWordExprIsZero(expr: PtExpression, falseLabel: String) {
// if w==0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | bne $falseLabel")
}
}
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
// optimized code for byte comparisons with 0
assignConditionValueToRegisterAndTest(condition.left)
when (condition.operator) {
"==" -> asmgen.out(" bne $falseLabel")
"!=" -> asmgen.out(" beq $falseLabel")
">" -> {
if(signed) asmgen.out(" bmi $falseLabel | beq $falseLabel")
else asmgen.out(" beq $falseLabel")
}
">=" -> {
if(signed) asmgen.out(" bmi $falseLabel")
else { /* always true for unsigned */ }
}
"<" -> {
if(signed) asmgen.out(" bpl $falseLabel")
else asmgen.jmp(falseLabel)
}
"<=" -> {
if(signed) {
// inverted '>'
asmgen.out("""
beq +
bpl $falseLabel
+""")
} else asmgen.out(" bne $falseLabel")
}
else -> throw AssemblyError("expected comparison operator")
}
}
private fun translateIfCompareWithZeroByte(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
// optimized code for byte comparisons with 0
val condition = stmt.condition as PtBinaryExpression
@@ -1741,6 +2048,51 @@ _jump jmp ($asmLabel)
}
}
private fun translateFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
val constValue = (condition.right as? PtNumber)?.number
if(constValue==0.0) {
if (condition.operator == "==") {
// if FL==0.0
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN | cmp #0 | bne $elseLabel")
return
} else if(condition.operator=="!=") {
// if FL!=0.0
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN | cmp #0 | beq $elseLabel")
return
}
}
when(condition.operator) {
"==" -> {
translateFloatsEqualsConditionIntoA(condition.left, condition.right)
asmgen.out(" beq $elseLabel")
}
"!=" -> {
translateFloatsEqualsConditionIntoA(condition.left, condition.right)
asmgen.out(" bne $elseLabel")
}
"<" -> {
translateFloatsLessConditionIntoA(condition.left, condition.right, false)
asmgen.out(" beq $elseLabel")
}
"<=" -> {
translateFloatsLessConditionIntoA(condition.left, condition.right, true)
asmgen.out(" beq $elseLabel")
}
">" -> {
translateFloatsLessConditionIntoA(condition.left, condition.right, true)
asmgen.out(" bne $elseLabel")
}
">=" -> {
translateFloatsLessConditionIntoA(condition.left, condition.right, false)
asmgen.out(" bne $elseLabel")
}
else -> throw AssemblyError("expected comparison operator")
}
}
private fun translateFloatsEqualsConditionIntoA(left: PtExpression, right: PtExpression) {
fun equalf(leftName: String, rightName: String) {
asmgen.out("""
@@ -32,10 +32,6 @@ internal class ProgramAndVarsGen(
internal fun generate() {
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "p8b_main" && allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main' or 'p8b_main'")
if(errors.noErrors()) {
program.allBlocks().forEach { block2asm(it) }
@@ -75,6 +71,12 @@ 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")
if(compTarget.name=="c64") {
if(options.floats)
asmgen.out("PROG8_C64_BANK_CONFIG=31 ; basic+IO+kernal")
else
asmgen.out("PROG8_C64_BANK_CONFIG=30 ; IO+kernal, no basic")
}
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
asmgen.out(".endweak")
@@ -89,6 +91,13 @@ internal class ProgramAndVarsGen(
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
OutputType.PRG -> {
when(options.launcher) {
@@ -98,30 +107,43 @@ internal class ProgramAndVarsGen(
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here")
asmgen.out("prog8_entrypoint")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
// this is the same as RAW
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
}
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
}
@@ -139,17 +161,20 @@ internal class ProgramAndVarsGen(
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr p8b_main.p8s_start | lda #31 | sta $01")
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c128" -> {
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
else -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
else -> asmgen.jmp("p8b_main.p8s_start")
}
}
@@ -265,6 +290,8 @@ internal class ProgramAndVarsGen(
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
}
}
asmgen.out(" ; memtop check")
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
}
private fun block2asm(block: PtBlock) {
@@ -272,12 +299,6 @@ internal class ProgramAndVarsGen(
asmgen.out("; ---- block: '${block.name}' ----")
if(block.options.address!=null)
asmgen.out("* = ${block.options.address!!.toHex()}")
else {
if(block.options.alignment==PtBlock.BlockAlignment.WORD)
asmgen.out("\t.align 2")
else if(block.options.alignment==PtBlock.BlockAlignment.PAGE)
asmgen.out("\t.align $100")
}
asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc"))
asmgen.outputSourceLine(block)
@@ -451,22 +472,11 @@ internal class ProgramAndVarsGen(
}
private fun entrypointInitialization() {
asmgen.out("; program startup initialization")
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
// set full BSS area to zero
asmgen.out("""
.if prog8_bss_section_size>0
; reset all variables in BSS section to zero
lda #<prog8_bss_section_start
ldy #>prog8_bss_section_start
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #<prog8_bss_section_size
ldy #>prog8_bss_section_size
lda #0
jsr prog8_lib.memset
.endif""")
// zero out the BSS area first, before setting the variable init values
// this is mainly to make sure the arrays are all zero'd out at program startup
asmgen.out(" jsr prog8_lib.program_startup_clear_bss")
// initialize block-level (global) variables at program start
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
@@ -509,17 +519,15 @@ internal class ProgramAndVarsGen(
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
outputStringvar(varname, it.value.second, it.value.first)
outputStringvar(varname, 0, it.value.second, it.value.first)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
arrayVariable2asm(varname, it.alloc.dt, 0, it.value, null)
}
asmgen.out("""+
clv
clc""")
asmgen.out("+")
}
private class ZpStringWithInitial(
@@ -540,8 +548,8 @@ internal class ProgramAndVarsGen(
for (variable in vars) {
val scopedName = variable.key
val svar = symboltable.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
if(svar?.initializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.initializationStringValue!!))
}
return result
}
@@ -552,8 +560,8 @@ internal class ProgramAndVarsGen(
for (variable in vars) {
val scopedName = variable.key
val svar = symboltable.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
if(svar?.initializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.initializationArrayValue!!))
}
return result
}
@@ -579,25 +587,44 @@ internal class ProgramAndVarsGen(
asmgen.out("")
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables without initialization value")
asmgen.out("; non-zeropage variables")
asmgen.out(" .section BSS")
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
uninitializedVariable2asm(it)
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt }).forEach {
uninitializedVariable2asm(it)
}
asmgen.out(" .send BSS")
}
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
asmgen.out("; non-zeropage variables with init value")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
stringvars.forEach {
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0 }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0 }
notAlignedStrings.forEach {
outputStringvar(
it.name,
it.onetimeInitializationStringValue!!.second,
it.onetimeInitializationStringValue!!.first
it.align,
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
}
othervars.sortedBy { it.type }.forEach {
alignedStrings.sortedBy { it.align }.forEach {
outputStringvar(
it.name,
it.align,
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
}
notAlignedOther.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
}
@@ -611,11 +638,13 @@ internal class ProgramAndVarsGen(
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
in SplitWordArrayTypes -> {
alignVar(variable.align)
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
}
in ArrayDatatypes -> {
alignVar(variable.align)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
asmgen.out("${variable.name}\t.fill $numbytes")
}
@@ -625,13 +654,18 @@ internal class ProgramAndVarsGen(
}
}
private fun alignVar(align: Int) {
if(align > 1)
asmgen.out(" .align ${align.toHex()}")
}
private fun staticVariable2asm(variable: StStaticVariable) {
val initialValue: Number =
if(variable.onetimeInitializationNumericValue!=null) {
if(variable.initializationNumericValue!=null) {
if(variable.dt== DataType.FLOAT)
variable.onetimeInitializationNumericValue!!
variable.initializationNumericValue!!
else
variable.onetimeInitializationNumericValue!!.toInt()
variable.initializationNumericValue!!.toInt()
} else 0
when (variable.dt) {
@@ -650,14 +684,17 @@ internal class ProgramAndVarsGen(
DataType.STR -> {
throw AssemblyError("all string vars should have been interned into prog")
}
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
in ArrayDatatypes -> {
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length)
}
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) {
private fun arrayVariable2asm(varname: String, dt: DataType, align: Int, value: StArray?, orNumberOfZeros: Int?) {
alignVar(align)
when(dt) {
DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
@@ -749,11 +786,16 @@ internal class ProgramAndVarsGen(
.filter { it is PtAsmSub && it.address!=null }
.forEach { asmsub ->
asmsub as PtAsmSub
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
val address = asmsub.address!!
val bank = if(address.constbank!=null) "; @bank ${address.constbank}"
else if(address.varbank!=null) "; @bank ${address.varbank?.name}"
else ""
asmgen.out(" ${asmsub.name} = ${address.address.toHex()} $bank")
}
}
private fun outputStringvar(varname: String, encoding: Encoding, value: String) {
private fun outputStringvar(varname: String, align: Int, 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())
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
@@ -49,8 +49,8 @@ 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 varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0 }
require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
var numberOfNonIntegerVariables = 0
@@ -89,7 +89,7 @@ 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 = varsDontCare.sortedByDescending { it.scopedName }
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedName }
for (variable in sortedList) {
if(variable.dt in IntegerDatatypesWithBoolean) {
if(zeropage.free.isEmpty()) {
@@ -175,17 +175,14 @@ internal class AnyExprAsmGen(
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
// this requires some more forced copying around of float values in certain cases
if (right.isSimple()) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
// C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK)
// otherwise the result of certain floating point operations such as FDIVT will be wrong.
// see https://www.c64-wiki.com/wiki/CONUPK
// Unfortunately this means we have to push and pop an intermediary floating point value to and from memory.
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
Cx16Target.NAME -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
@@ -1,8 +1,8 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StMemVar
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.StExtSub
import prog8.code.StExtSubParameter
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
@@ -11,11 +11,12 @@ import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.returnsWhatWhere
internal class AssignmentAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val options: CompilationOptions,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator) {
internal class AssignmentAsmGen(
private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val anyExprGen: AnyExprAsmGen,
private val allocator: VariableAllocator
) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
fun translate(assignment: PtAssignment) {
@@ -38,7 +39,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val sub = asmgen.symbolTable.lookup(values.name) as? StRomSub
val sub = asmgen.symbolTable.lookup(values.name) as? StExtSub
?: throw AssemblyError("only asmsubs can return multiple values")
require(sub.returns.size>=2)
@@ -47,7 +48,27 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.translate(values)
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if (statusFlagResults.isEmpty())
assignRegisterResults(registersResults)
else if(registersResults.isEmpty())
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
else
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
}
private fun assignStatusFlagsAndRegistersResults(
statusFlagResults: List<Pair<StExtSubParameter, PtNode>>,
registersResults: List<Pair<StExtSubParameter, PtNode>>
) {
fun needsToSaveA(registersResults: List<Pair<StExtSubParameter, PtNode>>): Boolean =
if(registersResults.isEmpty())
false
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
@@ -55,124 +76,123 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else
true
fun assignCarryFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
} else {
val saveA = needsToSaveA(registersResults)
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
assignRegisterResults(registersResults)
}
}
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StExtSubParameter, PtNode>>) {
// assigning flags to their variables targets requires load-instructions that destroy flags
// so if there's more than 1, we need to save and restore the flags
val saveFlags = statusFlagResults.size>1
fun hasFlag(statusFlagResults: List<Pair<StExtSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
for ((returns, target) in statusFlagResults) {
if(returns.register.statusflag!! == flag)
return target as PtAssignTarget
}
return null
}
fun assignZeroFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc)
val targetZero = hasFlag(statusFlagResults, Statusflag.Pz)
val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn)
val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv)
if(saveA) asmgen.out(" pha")
if(targetZero!=null && !targetZero.void)
assignZeroFlagResult(targetZero, saveFlags)
if(targetNeg!=null && !targetNeg.void)
assignNegativeFlagResult(targetNeg, saveFlags)
if(targetCarry!=null && !targetCarry.void)
assignCarryFlagResult(targetCarry)
if(targetOverflow!=null && !targetOverflow.void)
assignOverflowFlagResult(targetOverflow)
if(saveA) asmgen.out(" pla")
}
private fun assignRegisterResults(registersResults: List<Pair<StExtSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
private fun assignCarryFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
private fun assignZeroFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
fun assignNegativeFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
private fun assignNegativeFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
if(saveFlags) asmgen.out(" php")
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveFlags) asmgen.out(" plp")
}
fun assignOverflowFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
private fun assignOverflowFlagResult(target: PtAssignTarget) {
// overflow is not clobbered so no need to save/restore it
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if(statusFlagResults.isNotEmpty()) {
if(statusFlagResults.size>1)
TODO("handle multiple status flag results")
val (returns, target) = statusFlagResults.single()
target as PtAssignTarget
if(target.void) {
// forget about the Carry status flag, only assign the normal return values
assignRegisterResults(registersResults)
return
}
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
return when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, false)
Statusflag.Pz -> assignZeroFlagResult(target, false)
Statusflag.Pv -> assignOverflowFlagResult(target, false)
Statusflag.Pn -> assignNegativeFlagResult(target, false)
}
}
val saveA = needsToSaveA(registersResults)
when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, saveA)
Statusflag.Pz -> assignZeroFlagResult(target, saveA)
Statusflag.Pv -> assignOverflowFlagResult(target, saveA)
Statusflag.Pn -> assignNegativeFlagResult(target, saveA)
}
}
assignRegisterResults(registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
}
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
when(assign.source.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
@@ -201,7 +221,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.BOOL -> {
if (assign.source.datatype == DataType.BOOL || !options.strictBool) assignVariableByte(assign.target, variable)
if (assign.source.datatype == DataType.BOOL) assignVariableByte(assign.target, variable)
else throw AssemblyError("assigning non-bool variable to boolean, should have been typecasted")
}
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
@@ -335,7 +355,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
is PtNumber -> {
val indexValue = index.number.toString()
val indexValue = index.number.toInt().toString()
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
@@ -384,7 +404,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
is PtNumber -> {
val indexValue = index.number.toString()
val indexValue = index.number.toInt().toString()
asmgen.out("""
tax
lda $pointer
@@ -619,6 +639,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
throw AssemblyError("Expression is too complex to translate into assembly. Split it up into several separate statements, introduce a temporary variable, or otherwise rewrite it. Location: $pos")
}
}
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
else -> throw AssemblyError("weird assignment value type $value")
}
}
@@ -915,7 +936,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
if(target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
@@ -924,7 +945,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
DataType.UWORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_uw_asm")
asmgen.out(" jsr prog8_math.divmod_uw_asm")
assignVariableWord(target, "P8ZP_SCRATCH_W2", DataType.UWORD)
return true
}
@@ -940,7 +961,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
assignRegisterByte(target, CpuRegister.Y, false, true)
return true
}
@@ -949,19 +970,19 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr math.divmod_b_asm")
asmgen.out(" jsr prog8_math.divmod_b_asm")
assignRegisterByte(target, CpuRegister.Y, true, true)
return true
}
DataType.UWORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_uw_asm")
asmgen.out(" jsr prog8_math.divmod_uw_asm")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
DataType.WORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_w_asm")
asmgen.out(" jsr prog8_math.divmod_w_asm")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@@ -978,7 +999,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, expr.type in SignedDatatypes)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr math.multiply_bytes")
asmgen.out(" jsr prog8_math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
@@ -1001,8 +1022,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
asmgen.out(" jsr math.multiply_words")
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
asmgen.out(" jsr prog8_math.multiply_words")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
return true
@@ -1014,16 +1035,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
in ByteDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type in SignedDatatypes)
if (value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr math.mul_byte_${value}")
asmgen.out(" jsr prog8_math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
in WordDatatypes -> {
if (value in asmgen.optimizedWordMultiplications) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type in SignedDatatypes)
asmgen.out(" jsr math.mul_word_${value}")
asmgen.out(" jsr prog8_math.mul_word_${value}")
}
else {
if(expr.definingBlock()!!.options.veraFxMuls){
@@ -1034,8 +1055,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sty cx16.r0+1
jsr verafx.muls""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
asmgen.out(" jsr math.multiply_words")
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
asmgen.out(" jsr prog8_math.multiply_words")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
@@ -1072,11 +1093,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(expr.operator==">>")
if(signed)
asmgen.out(" jsr math.lsr_byte_A")
asmgen.out(" jsr prog8_math.lsr_byte_A")
else
asmgen.out(" jsr math.lsr_ubyte_A")
asmgen.out(" jsr prog8_math.lsr_ubyte_A")
else
asmgen.out(" jsr math.asl_byte_A")
asmgen.out(" jsr prog8_math.asl_byte_A")
assignRegisterByte(target, CpuRegister.A, signed, true)
return true
} else {
@@ -1084,11 +1105,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.restoreRegisterStack(CpuRegister.X, true)
if(expr.operator==">>")
if(signed)
asmgen.out(" jsr math.lsr_word_AY")
asmgen.out(" jsr prog8_math.lsr_word_AY")
else
asmgen.out(" jsr math.lsr_uword_AY")
asmgen.out(" jsr prog8_math.lsr_uword_AY")
else
asmgen.out(" jsr math.asl_word_AY")
asmgen.out(" jsr prog8_math.asl_word_AY")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@@ -1108,7 +1129,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(shifts==1)
asmgen.out(" cmp #$80 | ror a")
else
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
asmgen.out(" ldy #$shifts | jsr prog8_math.lsr_byte_A")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
@@ -1120,7 +1141,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else -> {
if(signed && expr.operator==">>") {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
asmgen.out(" ldy #$shifts | jsr prog8_math.lsr_byte_A")
} else {
asmgen.out(" lda #0")
}
@@ -1153,7 +1174,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
ror a""")
}
else
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
asmgen.out(" ldx #$shifts | jsr prog8_math.lsr_word_AY")
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
@@ -1177,16 +1198,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.out(" ldx #$shifts")
if(signed)
asmgen.out(" jsr math.lsr_word_AY")
asmgen.out(" jsr prog8_math.lsr_word_AY")
else
asmgen.out(" jsr math.lsr_uword_AY")
asmgen.out(" jsr prog8_math.lsr_uword_AY")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
else -> {
if(signed && expr.operator==">>") {
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
asmgen.out(" ldx #$shifts | jsr prog8_math.lsr_word_AY")
} else {
asmgen.out(" lda #0 | ldy #0")
}
@@ -1483,11 +1504,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
val address = mem.address as? PtBinaryExpression
if(address is PtBinaryExpression) {
val constOffset = address.right.asConstInteger()
// check that the offset is actually a byte (so that it fits in a single register)
if(constOffset==null && address.right.type !in ByteDatatypes)
return false
if(constOffset!=null && constOffset !in -128..255)
return false
val ptrVar = address.left as? PtIdentifier
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
val constOffset = address.right.asConstInteger()
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
asmgen.out(" ldy #$constOffset")
@@ -1805,9 +1831,56 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun containmentCheckIntoA(containment: PtContainmentCheck) {
val elementDt = containment.element.type
val symbol = asmgen.symbolTable.lookup(containment.iterable.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingSub())
val elementDt = containment.needle.type
if(containment.haystackValues!=null) {
val haystack = containment.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in ByteDatatypesWithBoolean -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
for(number in haystack) {
asmgen.out("""
cmp #$number
beq +""")
}
asmgen.out("""
lda #0
beq ++
+ lda #1
+""")
}
in WordDatatypes -> {
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
assignExpressionToRegister(containment.needle, RegisterOrPair.AY, elementDt == DataType.WORD)
val gottemLabel = asmgen.makeLabel("gottem")
val endLabel = asmgen.makeLabel("end")
for(number in haystack) {
asmgen.out("""
cmp #<$number
bne +
cpy #>$number
beq $gottemLabel
+ """)
}
asmgen.out("""
lda #0
beq $endLabel
$gottemLabel lda #1
$endLabel""")
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
return
}
val symbol = asmgen.symbolTable.lookup(containment.haystackHeapVar!!.name)!!
val symbolName = asmgen.asmVariableName(symbol, containment.definingISub())
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
@@ -1815,7 +1888,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
when(dt) {
DataType.STR -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position,"P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1823,13 +1896,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_F -> {
assignExpressionToRegister(containment.element, RegisterOrPair.FAC1, true)
assignExpressionToRegister(containment.needle, RegisterOrPair.FAC1, true)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr floats.containment_floatarray")
}
DataType.ARRAY_B, DataType.ARRAY_UB -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
asmgen.out(" pla")
@@ -1837,7 +1910,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt)
assignExpressionToVariable(containment.needle, "P8ZP_SCRATCH_W1", elementDt)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W2"), symbolName, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_wordarray")
@@ -225,7 +225,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.LITERALNUMBER -> inplacemodificationSplitWordWithLiteralval(target.asmVarname, target.datatype, index, operator, value.number!!.number.toInt())
else -> {
// TODO: more optimized code for VARIABLE, REGISTER, MEMORY, ARRAY, EXPRESSION in the case of split-word arrays
val scope = target.origAstTarget?.definingSub()
val scope = target.origAstTarget?.definingISub()
val regTarget = AsmAssignTarget.fromRegisters(RegisterOrPair.R0, false, target.position, scope, asmgen)
val assignToReg = AsmAssignment(value, regTarget, program.memsizer, target.position)
assignmentAsmGen.translateNormalAssignment(assignToReg, scope)
@@ -904,9 +904,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
when (operator) {
"+" -> asmgen.out(" clc | adc $otherName")
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm")
"*" -> 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
@@ -981,23 +981,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"*" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr math.mul_byte_${value}")
asmgen.out(" jsr prog8_math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"/" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm | tya")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"%" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
@@ -1143,17 +1143,17 @@ $shortcutLabel:""")
when (operator) {
"+" -> asmgen.out(" clc | adc $variable")
"-" -> asmgen.out(" sec | sbc $variable")
"*" -> asmgen.out(" ldy $variable | jsr math.multiply_bytes")
"*" -> asmgen.out(" ldy $variable | jsr prog8_math.multiply_bytes")
"/" -> {
if(signed)
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_b_asm | tya")
else
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1318,14 +1318,14 @@ $shortcutLabel:""")
}
"/" -> {
if(signed)
asmgen.out(" tay | lda $variable | jsr math.divmod_b_asm | tya")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_b_asm | tya")
else
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm | tya")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
}
"<<" -> {
asmgen.out("""
@@ -1478,16 +1478,16 @@ $shortcutLabel:""")
}
"*" -> {
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
asmgen.out(" lda $name | jsr prog8_math.mul_byte_$value | sta $name")
else
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.multiply_bytes | sta $name")
}
"/" -> {
// replacing division by shifting is done in an optimizer step.
if (dt == DataType.UBYTE)
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.divmod_ub_asm | sty $name")
else
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_b_asm | sty $name")
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.divmod_b_asm | sty $name")
}
"%" -> {
if(dt==DataType.BYTE)
@@ -1495,7 +1495,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr math.divmod_ub_asm
jsr prog8_math.divmod_ub_asm
sta $name""")
}
"<<" -> {
@@ -1529,7 +1529,7 @@ $shortcutLabel:""")
value>3 -> asmgen.out("""
lda $name
ldy #$value
jsr math.lsr_byte_A
jsr prog8_math.lsr_byte_A
sta $name""")
else -> repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
}
@@ -1802,7 +1802,7 @@ $shortcutLabel:""")
"*" -> {
// the mul code works for both signed and unsigned
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $lsb | ldy $msb | jsr math.mul_word_$value | sta $lsb | sty $msb")
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
} else {
if(block?.options?.veraFxMuls==true)
// cx16 verafx hardware mul
@@ -1821,12 +1821,12 @@ $shortcutLabel:""")
else
asmgen.out("""
lda $lsb
sta math.multiply_words.multiplier
sta prog8_math.multiply_words.multiplier
lda $msb
sta math.multiply_words.multiplier+1
sta prog8_math.multiply_words.multiplier+1
lda #<$value
ldy #>$value
jsr math.multiply_words
jsr prog8_math.multiply_words
sta $lsb
sty $msb""")
}
@@ -1844,7 +1844,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
jsr prog8_math.divmod_w_asm
sta $lsb
sty $msb
""")
@@ -1857,7 +1857,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
sta $lsb
sty $msb
""")
@@ -1876,7 +1876,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sta $lsb
@@ -2297,20 +2297,20 @@ $shortcutLabel:""")
sty $name+1""")
} else {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz math.multiply_words.multiplier+1")
asmgen.out(" stz prog8_math.multiply_words.multiplier+1")
else
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
asmgen.out(" lda #0 | sta prog8_math.multiply_words.multiplier+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
asmgen.out(" sta prog8_math.multiply_words.multiplier | sty prog8_math.multiply_words.multiplier+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
jsr prog8_math.multiply_words
sta $name
sty $name+1""")
}
@@ -2324,7 +2324,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
sta $name
sty $name+1
""")
@@ -2336,7 +2336,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_w_asm
jsr prog8_math.divmod_w_asm
sta $name
sty $name+1
""")
@@ -2352,7 +2352,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta $name
lda P8ZP_SCRATCH_W2+1
@@ -2460,11 +2460,11 @@ $shortcutLabel:""")
asmgen.out("""
lda $otherName
ldy $otherName+1
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
jsr prog8_math.multiply_words
sta $name
sty $name+1""")
}
@@ -2477,7 +2477,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy $otherName+1
jsr math.divmod_w_asm
jsr prog8_math.divmod_w_asm
sta $name
sty $name+1""")
}
@@ -2489,7 +2489,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy $otherName+1
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
sta $name
sty $name+1""")
}
@@ -2504,7 +2504,7 @@ $shortcutLabel:""")
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy $otherName+1
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta $name
lda P8ZP_SCRATCH_W2+1
@@ -2661,11 +2661,11 @@ $shortcutLabel:""")
""")
else
asmgen.out("""
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
jsr prog8_math.multiply_words
sta $name
sty $name+1
""")
@@ -2680,9 +2680,9 @@ $shortcutLabel:""")
sta P8ZP_SCRATCH_W1+1
txa""")
if (dt == DataType.WORD)
asmgen.out(" jsr math.divmod_w_asm")
asmgen.out(" jsr prog8_math.divmod_w_asm")
else
asmgen.out(" jsr math.divmod_uw_asm")
asmgen.out(" jsr prog8_math.divmod_uw_asm")
asmgen.out(" sta $name | sty $name+1")
}
@@ -2696,7 +2696,7 @@ $shortcutLabel:""")
lda $name+1
sta P8ZP_SCRATCH_W1+1
txa
jsr math.divmod_uw_asm
jsr prog8_math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sta $name
+39 -6
View File
@@ -26,7 +26,8 @@ class TestCodegen: FunSpec({
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
}
@@ -43,14 +44,46 @@ class TestCodegen: FunSpec({
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502(prefixSymbols = false)
val codegen = AsmGen6502(prefixSymbols = false, 0)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable(
"pi",
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
PtNumber(DataType.UBYTE, 0.0, Position.DUMMY),
null,
Position.DUMMY
))
sub.add(PtVariable(
"particleX",
DataType.ARRAY_UB,
ZeropageWish.DONTCARE,
0u,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"particleDX",
DataType.ARRAY_UB,
ZeropageWish.DONTCARE,
0u,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"xx",
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
PtNumber(DataType.WORD, 1.0, Position.DUMMY),
null,
Position.DUMMY
))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(false, Position.DUMMY).also {
+1
View File
@@ -31,6 +31,7 @@ dependencies {
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
@@ -1,7 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.StExtSub
import prog8.code.StExtSubParameter
import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
@@ -14,7 +14,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub
val sub = codeGen.symbolTable.lookup(values.name) as? StExtSub
?: throw AssemblyError("only asmsubs can return multiple values")
val result = mutableListOf<IRCodeChunkBase>()
@@ -46,7 +46,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
private fun assignCpuRegister(returns: StRomSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val loadCpuRegInstr = when(returns.register.registerOrPair) {
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
@@ -57,10 +57,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
null -> {
when(returns.register.statusflag) {
Statusflag.Pc -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
else -> throw AssemblyError("weird statusflag as returnvalue")
}
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
else -> throw AssemblyError("cannot load register")
}
@@ -281,7 +278,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
"not" -> {
// TODO: in boolean branch, is 'not' handled ok like this?
val register = codeGen.registers.nextFree()
if(constIndex!=null) {
result += IRCodeChunk(null, null).also {
@@ -363,7 +359,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val variable = targetArray.variable.name
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
val fixedIndex = constIntValue(targetArray.index)
val fixedIndex = targetArray.index.asConstInteger()
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
if(zero) {
if(fixedIndex!=null) {
@@ -887,7 +883,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
private fun operatorMultiplyInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? {
return null // TODO("inplace split word array *")
return null // TODO("inplace split word array *")
}
private fun operatorMinusInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? {
@@ -1374,7 +1370,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun createInplaceArrayComparison(array: PtArrayIndexer, value: PtExpression, comparisonOperator: String): IRCodeChunks? {
if(array.type==DataType.FLOAT)
return null // TODO("optimized in-place compare on float arrays")) // TODO?
return null // TODO("optimized in-place compare on float arrays"))
val eltSize = codeGen.program.memsizer.memorySize(array.type)
val result = mutableListOf<IRCodeChunkBase>()
@@ -1393,10 +1389,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
">=" -> return null // TODO("array >=0 inplace")) // TODO?
"<" -> return null // TODO("array <0 inplace"))
"<=" -> return null // TODO("array <=0 inplace"))
">" -> return null // TODO("array >0 inplace"))
">=" -> return null // TODO("array >=0 inplace"))
else -> throw AssemblyError("invalid operator")
}
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
@@ -1416,10 +1412,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
">=" -> return null // TODO("array >=0 inplace")) // TODO?
"<" -> return null // TODO("array <0 inplace"))
"<=" -> return null // TODO("array <=0 inplace"))
">" -> return null // TODO("array >0 inplace"))
">=" -> return null // TODO("array >=0 inplace"))
else -> throw AssemblyError("invalid operator")
}
it += IRInstruction(Opcode.STOREX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
@@ -17,6 +17,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
"call" -> funcCall(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
@@ -38,77 +39,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"ror" -> funcRolRor(call)
"rol2" -> funcRolRor(call)
"ror2" -> funcRolRor(call)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse/ifExpression statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse/ifExpression statement")
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_arraycopy" -> funcArrayCopy(call)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
private fun funcArrayCopy(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val source = call.args[0] as PtIdentifier
val target = call.args[1] as PtIdentifier
val sourceLength = codeGen.symbolTable.getLength(source.name)!!
val targetLength = codeGen.symbolTable.getLength(target.name)!!
require(sourceLength==targetLength)
val result = mutableListOf<IRCodeChunkBase>()
val fromReg = codeGen.registers.nextFree()
val toReg = codeGen.registers.nextFree()
val countReg = codeGen.registers.nextFree()
if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) {
// split words -> split words, copy lsb and msb arrays separately
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
}
}
else if(source.type in SplitWordArrayTypes) {
// split -> normal words
require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W)
val fromRegMsb = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromRegMsb, labelSymbol = source.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
}
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to fromRegMsb, IRDataType.WORD to toReg, IRDataType.BYTE to countReg), returns = null)
}
else if(target.type in SplitWordArrayTypes) {
// normal -> split words
require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W)
val toRegMsb = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toRegMsb, labelSymbol = target.name+"_msb")
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
}
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.WORD to toRegMsb, IRDataType.BYTE to countReg), returns = null)
}
else {
// normal array to array copy (various element types)
val eltsize = codeGen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type))
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength * eltsize)
}
result += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])
@@ -149,6 +88,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
}
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentA = exprGen.translateExpression(call.args[2])
val argumentX = exprGen.translateExpression(call.args[3])
val argumentY = exprGen.translateExpression(call.args[4])
val argumentCarry = exprGen.translateExpression(call.args[5])
addToResult(result, bankTr, bankTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addToResult(result, argumentA, argumentA.resultReg, -1)
addToResult(result, argumentX, argumentX.resultReg, -1)
addToResult(result, argumentY, argumentY.resultReg, -1)
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg,
IRDataType.BYTE to argumentA.resultReg,
IRDataType.BYTE to argumentX.resultReg,
IRDataType.BYTE to argumentY.resultReg,
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
}
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val number = call.args[0]
@@ -580,6 +542,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
if(arr.splitWords) TODO("IR rol/ror on split words array")
val variable = arr.variable.name
val itemsize = codeGen.program.memsizer.memorySize(arr.type)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = index*itemsize), null)
@@ -1,7 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StNode
import prog8.code.StRomSub
import prog8.code.StExtSub
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
@@ -79,6 +79,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtPrefix -> translate(expr)
is PtArrayIndexer -> translate(expr)
is PtBinaryExpression -> translate(expr)
is PtIfExpression -> translate(expr)
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
is PtFunctionCall -> translate(expr)
is PtContainmentCheck -> translate(expr)
@@ -89,6 +90,41 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translate(ifExpr: PtIfExpression): ExpressionCodeResult {
if((ifExpr.condition as? PtPrefix)?.operator=="not")
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
val condTr = translateExpression(ifExpr.condition)
val trueTr = translateExpression(ifExpr.truevalue)
val falseTr = translateExpression(ifExpr.falsevalue)
val irDt = irType(ifExpr.type)
val result = mutableListOf<IRCodeChunkBase>()
val falseLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
addToResult(result, condTr, condTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null)
if (irDt != IRDataType.FLOAT) {
addToResult(result, trueTr, trueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(falseLabel, null)
addToResult(result, falseTr, trueTr.resultReg, -1)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
} else {
addToResult(result, trueTr, -1, trueTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(falseLabel, null)
addToResult(result, falseTr, -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
@@ -168,55 +204,93 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val elementDt = check.needle.type
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
if(check.haystackValues!=null) {
val haystack = check.haystackValues!!.children.map {
if(it is PtBool) it.asInt()
else (it as PtNumber).number.toInt()
}
when(elementDt) {
in IntegerDatatypesWithBoolean -> {
if (elementDt in ByteDatatypesWithBoolean) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt in WordDatatypes) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
for(value in haystack){
it += IRInstruction(Opcode.CMPI, irType(elementDt), elementTr.resultReg, immediate = value)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = gottemLabel)
}
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 1), gottemLabel)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
else -> throw AssemblyError("weird dt $elementDt")
}
}
val haystackVar = check.haystackHeapVar!!
when(haystackVar.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(check.iterable)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.nextFree()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
else -> throw AssemblyError("weird iterable dt ${haystackVar.type} for ${haystackVar.name}")
}
}
@@ -454,7 +528,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
if(callTarget.scopedName 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)
}
@@ -514,7 +588,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
}
is StRomSub -> {
is StExtSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
@@ -534,6 +608,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> TODO("floating point register parameters not supported")
in Cx16VirtualRegisters -> {
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
}
@@ -547,7 +622,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
if(callTarget.returns.size>1)
return callRomSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
return callExtSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
// return a single value (or nothing)
val returnRegSpec = if(fcall.void) null else {
@@ -569,8 +644,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else {
val address = callTarget.address!!
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
}
else {
TODO("callfar into another bank is not implemented for the selected compilation target")
}
}
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
@@ -649,12 +734,25 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
}
"floats.push" -> {
// push float
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, -1)
}
"floats.pop" -> {
// pop float
val popReg = codeGen.registers.nextFreeFloat()
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = popReg), null)
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, resultFpReg = popReg)
}
else -> throw AssemblyError("unknown stack subroutine called")
}
}
private fun callRomSubWithMultipleReturnValues(
callTarget: StRomSub,
private fun callExtSubWithMultipleReturnValues(
callTarget: StExtSub,
fcall: PtFunctionCall,
argRegisters: MutableList<FunctionCallArgs.ArgumentSpec>,
result: MutableList<IRCodeChunkBase>
@@ -668,8 +766,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else {
val address = callTarget.address!!
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
}
else TODO("extsub with banked address got called ${callTarget.name}")
}
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
@@ -788,7 +895,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
} else {
return if(constValue(binExpr.right)==0.0) {
return if(binExpr.right.asConstValue()==0.0) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val opcode = if (notEquals) Opcode.SNZ else Opcode.SZ
@@ -23,6 +23,7 @@ class IRCodeGen(
makeAllNodenamesScoped(program)
moveAllNestedSubroutinesToBlockScope(program)
verifyNameScoping(program, symbolTable)
changeGlobalVarInits(symbolTable)
val irSymbolTable = IRSymbolTable.fromStDuringCodegen(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
@@ -54,12 +55,43 @@ class IRCodeGen(
return irProg
}
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
// Normally, block level (global) variables that have a numeric initialization value
// are initialized via an assignment statement.
val initsToRemove = mutableListOf<Pair<PtBlock, PtAssignment>>()
symbolTable.allVariables.forEach { variable ->
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
val block = variable.parent.astNode as PtBlock
val initialization = (block.children.firstOrNull {
it is PtAssignment && it.target.identifier?.name==variable.scopedName
} as PtAssignment?)
val initValue = initialization?.value
when(initValue){
is PtBool -> {
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
initsToRemove += block to initialization
}
is PtNumber -> {
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}
is PtArray, is PtString -> throw AssemblyError("array or string initialization values should already be part of the vardecl, not a separate assignment")
else -> {}
}
}
}
for((block, assign) in initsToRemove) {
block.children.remove(assign)
}
}
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
fun verifyPtNode(node: PtNode) {
when (node) {
is PtBuiltinFunctionCall -> require('.' !in node.name) { "builtin function call name should not be scoped: ${node.name}" }
is PtFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtIdentifier -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtAsmSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtBlock -> require('.' !in node.name) { "block name should not be scoped: ${node.name}" }
is PtConstant -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
@@ -69,8 +101,7 @@ 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}" }
else -> { /* node has no name */
}
else -> { /* node has no name or is ok to have no dots in the name */ }
}
node.children.forEach { verifyPtNode(it) }
}
@@ -201,6 +232,7 @@ class IRCodeGen(
chunk += IRInstruction(Opcode.BREAKPOINT)
listOf(chunk)
}
is PtAlign -> TODO("ir support for inline %align")
is PtConditionalBranch -> translate(node)
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
@@ -221,6 +253,7 @@ 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}")
else -> TODO("missing codegen for $node")
@@ -487,49 +520,76 @@ class IRCodeGen(
val loopLabel = createLabelName()
val result = mutableListOf<IRCodeChunkBase>()
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
if(loopvarDtIr==IRDataType.BYTE && step==-1 && iterable.to.asConstInteger()==0) {
// downto 0 optimization (byte)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 255)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
else if(step==-1 && iterable.to.asConstInteger()==1) {
// downto 1 optimization (byte and word)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else {
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
}
return result
}
@@ -558,9 +618,25 @@ class IRCodeGen(
result += chunk
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) {
// downto 0 optimization (byte)
if(loopvarDt==DataType.BYTE || iterable.first<=127) {
chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel)
} else {
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else if(iterable.step==-1 && iterable.last==1) {
// downto 1 optimization (byte and word)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
} else {
// downto some other value
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
result += chunk2
return result
}
@@ -1252,6 +1328,9 @@ class IRCodeGen(
}
private fun translateIfElse(ifElse: PtIfElse): IRCodeChunks {
if((ifElse.condition as? PtPrefix)?.operator=="not" && ifElse.hasElse())
throw AssemblyError("not prefix in ifelse should have been replaced by swapped if-else blocks")
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null || condition.left.type != DataType.FLOAT) {
return ifWithElse_IntegerCond(ifElse)
@@ -1317,12 +1396,14 @@ class IRCodeGen(
private fun ifWithElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) {
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
val tr = expressionEval.translateExpression(condition)
if(addCmpiZero)
tr.chunks.last().instructions.add(IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0))
result += tr.chunks
if(ifElse.hasElse()) {
val elseLabel = createLabelName()
@@ -1342,7 +1423,7 @@ class IRCodeGen(
fun translateBinExpr(condition: PtBinaryExpression) {
if(condition.operator in LogicalOperators)
return translateSimple(condition, Opcode.BSTEQ)
return translateSimple(condition, Opcode.BSTEQ, false)
val signed = condition.left.type in SignedDatatypes
val elseBranchFirstReg: Int
@@ -1481,18 +1562,21 @@ class IRCodeGen(
when(val cond=ifElse.condition) {
is PtBool -> {
// normally this will be optimized away, but not with -noopt
translateSimple(cond, Opcode.BSTEQ)
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
translateSimple(cond, Opcode.BSTEQ)
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
translateSimple(cond, Opcode.BSTEQ)
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtBuiltinFunctionCall, is PtFunctionCall -> {
translateSimple(cond, Opcode.BSTEQ, true)
}
is PtPrefix -> {
require(cond.operator=="not")
translateSimple(cond.value, Opcode.BSTNE)
translateSimple(cond.value, Opcode.BSTNE, false)
}
is PtBinaryExpression -> {
translateBinExpr(cond)
@@ -1503,7 +1587,7 @@ class IRCodeGen(
}
private fun translate(repeat: PtRepeatLoop): IRCodeChunks {
when (constIntValue(repeat.count)) {
when (repeat.count.asConstInteger()) {
0 -> return emptyList()
1 -> return translateGroup(repeat.children)
256 -> {
@@ -1514,18 +1598,30 @@ class IRCodeGen(
val repeatLabel = createLabelName()
val skipRepeatLabel = createLabelName()
val irDt = irType(repeat.count.type)
val constRepeats = repeat.count.asConstInteger()
val result = mutableListOf<IRCodeChunkBase>()
val countTr = expressionEval.translateExpression(repeat.count)
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
if(constRepeats==65536) {
// make use of the word wrap around to count to 65536
val resultRegister = registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, immediate = 0), null)
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
} else {
val irDt = irType(repeat.count.type)
val countTr = expressionEval.translateExpression(repeat.count)
addToResult(result, countTr, countTr.resultReg, -1)
if (repeat.count.asConstValue() == null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
}
result += IRCodeChunk(skipRepeatLabel, null)
return result
@@ -1570,14 +1666,22 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.RETURN), null)
} else {
if(value.type==DataType.FLOAT) {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
if(value is PtNumber) {
addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null)
} else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
}
}
else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type) , reg1=tr.resultReg), null)
if(value.asConstInteger()!=null) {
addInstr(result, IRInstruction(Opcode.RETURNI, irType(value.type), immediate = value.asConstInteger()), null)
} else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type), reg1 = tr.resultReg), null)
}
}
}
return result
@@ -1590,14 +1694,14 @@ class IRCodeGen(
block.options.forceOutput,
block.options.noSymbolPrefixing,
block.options.veraFxMuls,
block.options.ignoreUnused,
translate(block.options.alignment)
block.options.ignoreUnused
), block.position)
for (child in block.children) {
when(child) {
is PtNop -> { /* nothing */ }
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtAlign -> TODO("ir support for inline %align")
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
for (subchild in child.children) {
@@ -1607,9 +1711,9 @@ class IRCodeGen(
}
is PtAsmSub -> {
if(child.address!=null) {
// romsub. No codegen needed: calls to this are jumping straight to the address.
// extmsub. No codegen needed: calls to this are jumping straight to the address.
require(child.children.isEmpty()) {
"romsub should be empty at ${child.position}"
"extsub should be empty at ${child.position}"
}
} else {
// regular asmsub
@@ -1622,7 +1726,7 @@ class IRCodeGen(
)
irBlock += IRAsmSubroutine(
child.name,
child.address,
null,
child.clobbers,
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
@@ -1648,25 +1752,17 @@ class IRCodeGen(
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = it.definingSub()!!.name + "." + it.name
val flattenedName = it.definingISub()!!.name + "." + it.name
if(symbolTable.lookup(flattenedName)==null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
IRSubroutine.IRParam(flattenedName, orig.dt)
}
private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment {
return when(alignment) {
PtBlock.BlockAlignment.NONE -> IRBlock.BlockAlignment.NONE
PtBlock.BlockAlignment.WORD -> IRBlock.BlockAlignment.WORD
PtBlock.BlockAlignment.PAGE -> IRBlock.BlockAlignment.PAGE
}
}
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
return "label_gen_$labelSequenceNumber"
return "${PtLabel.GeneratedLabelPrefix}$labelSequenceNumber"
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
@@ -64,7 +64,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.CONCAT) {
if (ins.opcode == Opcode.CONCAT && idx>0) {
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
@@ -299,7 +299,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
// remove useless RETURN
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR)) {
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
chunk.instructions.removeAt(idx)
@@ -449,7 +449,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.forEach { (idx, ins) ->
if(ins.opcode==Opcode.STOREM) {
if(ins.opcode==Opcode.STOREM && idx>0) {
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOADM) {
// loadm.X rX,something | storem.X rX,something ?? -> get rid of the store.
@@ -22,7 +22,8 @@ class TestIRPeepholeOpt: FunSpec({
floats = false,
noSysInit = true,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
val prog = IRProgram("test", IRSymbolTable(), options, target)
prog.addBlock(block)
+96 -15
View File
@@ -23,7 +23,8 @@ class TestVmCodeGen: FunSpec({
floats = true,
noSysInit = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
}
@@ -44,10 +45,42 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable(
"pi",
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
PtNumber(DataType.UBYTE, 0.0, Position.DUMMY),
null,
Position.DUMMY
))
sub.add(PtVariable(
"particleX",
DataType.ARRAY_UB,
ZeropageWish.DONTCARE,
0u,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"particleDX",
DataType.ARRAY_UB,
ZeropageWish.DONTCARE,
0u,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"xx",
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
PtNumber(DataType.WORD, 1.0, Position.DUMMY),
null,
Position.DUMMY
))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(false, Position.DUMMY).also {
@@ -123,7 +156,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"f1",
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
@@ -186,7 +227,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"f1",
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
@@ -245,7 +294,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"f1",
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
@@ -292,7 +349,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"sb1",
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
@@ -355,7 +420,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"sb1",
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
@@ -414,7 +487,15 @@ class TestVmCodeGen: FunSpec({
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
sub.add(PtVariable(
"ub1",
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
@@ -442,9 +523,9 @@ class TestVmCodeGen: FunSpec({
irChunks.size shouldBe 1
}
test("romsub allowed in ir-codegen") {
test("extsub allowed in ir-codegen") {
//main {
// romsub $5000 = routine()
// extsub $5000 = routine()
//
// sub start() {
// routine()
@@ -453,8 +534,8 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(extsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
sub.add(call)
@@ -66,7 +66,7 @@ class ConstExprEvaluator {
// DataType.WORD -> result = result.toShort().toInt()
// else -> { /* keep as it is */ }
// }
return NumericLiteral.optimalNumeric(result.toDouble(), left.position)
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
@@ -133,7 +133,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -150,7 +150,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -167,7 +167,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
@@ -190,7 +190,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteral.optimalInteger(result, left.position)
NumericLiteral.optimalInteger(left.type, right.type, result, left.position)
}
DataType.FLOAT -> {
if(right.number==0.0) divideByZeroError(right.position)
@@ -219,7 +219,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
NumericLiteral.optimalNumeric(left.type, right.type, left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position)
@@ -2,6 +2,7 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.*
@@ -33,6 +34,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.type==DataType.LONG) {
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=DataType.LONG)
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
@@ -104,6 +113,28 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
}
if(expr.left.inferType(program).isArray) {
if (expr.operator=="*" && rightconst!=null) {
if (expr.left is ArrayLiteral) {
// concatenate array literal.
val part = expr.left as ArrayLiteral
if(part.value.isEmpty())
errors.warn("resulting array has length zero", part.position)
val tmp = mutableListOf<Expression>()
repeat(rightconst.number.toInt()) {
part.value.forEach { tmp += it.copy() }
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program)
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
}
}
}
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
@@ -164,13 +195,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
if (expr.operator == "-") {
// X - -1 ---> X + 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
else if (expr.operator == "+") {
// X + -1 ---> X - 1
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
}
@@ -290,6 +321,31 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(parent is VarDecl && parent.parent is Block) {
// only block level (global) initializers are considered here, because they're run just once at program startup
val const = arrayIndexedExpression.constValue(program)
if (const != null)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, const, parent))
val constIndex = arrayIndexedExpression.indexer.constIndex()
if (constIndex != null) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
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))
}
}
}
}
}
return noModifications
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null)
@@ -384,12 +440,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt istype DataType.LONG) {
return noModifications // this is handled in the numericalvalue case
}
if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
return noModifications
@@ -76,7 +76,7 @@ class VarConstantValueTypeAdjuster(
if (declValue != null) {
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position)
decl.value = null
return listOf(
IAstModification.ReplaceNode(decl, const, parent)
@@ -96,7 +96,7 @@ class VarConstantValueTypeAdjuster(
}
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.alignment, decl.dirty, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
@@ -268,11 +268,9 @@ class VarConstantValueTypeAdjuster(
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(
private val program: Program,
private val options: CompilationOptions,
private val errors: IErrorReporter
) : AstWalker() {
@@ -287,7 +285,7 @@ internal class ConstantIdentifierReplacer(
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if(identifier.parent is AssignTarget)
if(identifier.parent is AssignTarget || identifier.parent is Alias)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
@@ -392,110 +390,70 @@ internal class ConstantIdentifierReplacer(
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// convert a range expression that is assigned to an array, to an array literal instead.
val range = assignment.value as? RangeExpression
if(range!=null) {
val targetDatatype = assignment.target.inferType(program)
if(targetDatatype.isArray) {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOr(DataType.UNDEFINED),
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
assignment.value, false, false, 0u, false, Position.DUMMY)
val replaceValue = createConstArrayInitializerValue(decl)
if(replaceValue!=null) {
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
}
}
}
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
// convert the initializer range expression from a range or int, to an actual array.
val rangeSize=(decl.value as? RangeExpression)?.size()
if(rangeSize!=null && rangeSize>65535) {
errors.err("range size overflow", decl.value!!.position)
return null
}
val rangeExpr = decl.value as? RangeExpression ?: return null
// convert the initializer range expression from a range, to an actual array literal.
val declArraySize = decl.arraysize?.constIndex()
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the array size.
val fillvalue = numericLv.number
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
if(options.strictBool || numericLv.type !in ByteDatatypes)
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
DataType.ARRAY_F -> {
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
else -> return null
@@ -11,9 +11,9 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@@ -238,57 +238,41 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
if(expr.operator=="==") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool!=null) {
return if(rightConstBool)
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
if(rightConstBool==true) {
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
}
if (rightVal?.number == 1.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if (options.strictBool) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
if (expr.operator=="!=") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool!=null) {
return if (rightConstBool)
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
else
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(rightConstBool==false) {
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
}
if (rightVal?.number == 1.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if(options.strictBool) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
} else
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
@@ -426,21 +410,6 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
return noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="not") {
// not X <compare> Y -> X <invertedcompare> Y
val binExpr = expr.expression as? BinaryExpression
if(binExpr!=null) {
val invertedOperator = invertedComparisonOperator(binExpr.operator)
if(invertedOperator!=null) {
val inverted = BinaryExpression(binExpr.left, invertedOperator, binExpr.right, binExpr.position)
return listOf(IAstModification.ReplaceNode(expr, inverted, 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)
@@ -12,7 +12,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, options, errors)
val replacer = ConstantIdentifierReplacer(this, errors)
replacer.visit(this)
if (errors.noErrors()) {
replacer.applyModifications()
@@ -59,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
val opti = ExpressionSimplifier(this, options, errors)
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this, errors)
opti.visit(this)
return opti.applyModifications()
}
@@ -224,8 +224,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
noModifications
}
else -> {
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
if(origNode !== toInline) {
sub.hasBeenInlined = true
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
} else
noModifications
}
}
}
@@ -255,7 +258,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
is Return -> {
// is an expression, so we have to have a Return here in the inlined sub
// note that we don't have to process any args, because we online inline parameterless subroutines.
if(toInline.value!=null) {
if(toInline.value!=null && functionCallExpr!==toInline.value) {
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
}
@@ -127,19 +127,6 @@ class StatementOptimizer(private val program: Program,
)
}
}
// switch if/else around if the condition is a not
val prefixCond = ifElse.condition as? PrefixExpression
if(prefixCond?.operator=="not") {
errors.info("invert conditon and swap if/else blocks", ifElse.condition.position)
val newTruePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
val newElsePart = AnonymousScope(ifElse.truepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, newElsePart, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, newTruePart, ifElse),
IAstModification.ReplaceNode(ifElse.condition, prefixCond.expression, ifElse)
)
}
}
return noModifications
@@ -200,35 +187,6 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
fun incOrDec(inc: Boolean): Assignment {
val pos = forLoop.position
val loopVar = forLoop.loopVar
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
return Assignment(AssignTarget(loopVar.copy(), null, null, null, false, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = incOrDec(false)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, false, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
return noModifications
}
@@ -21,6 +21,31 @@ class UnusedCodeRemover(private val program: Program,
): AstWalker() {
private lateinit var callgraph: CallGraph
private val neverRemoveSubroutines = mutableListOf<Subroutine>()
init {
neverRemoveSubroutines.add(program.entrypoint)
program.allBlocks.singleOrNull { it.name=="sys" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pushw = subroutines.single { it.name == "pushw" }
val pop = subroutines.single { it.name == "pop" }
val popw = subroutines.single { it.name == "popw" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pushw)
neverRemoveSubroutines.add(pop)
neverRemoveSubroutines.add(popw)
}
program.allBlocks.singleOrNull { it.name=="floats" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pop = subroutines.single { it.name == "pop" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pop)
}
}
override fun before(program: Program): Iterable<IAstModification> {
callgraph = CallGraph(program)
@@ -92,7 +117,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock.options()
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.isAsmSubroutine) {
if (subroutine !in neverRemoveSubroutines && !forceOutput && !subroutine.isAsmSubroutine) {
if(callgraph.unused(subroutine)) {
if(subroutine.containsNoCodeNorVars) {
if("ignore_unused" !in subroutine.definingBlock.options())
+4 -2
View File
@@ -32,6 +32,7 @@ dependencies {
implementation project(':codeGenIntermediate')
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"
@@ -41,6 +42,7 @@ dependencies {
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.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
@@ -79,11 +81,11 @@ startScripts.enabled = true
application {
mainClass = 'prog8.CompilerMainKt'
applicationName = 'p8compile'
applicationName = 'prog8c'
}
shadowJar {
archiveBaseName = 'prog8compiler'
archiveBaseName = 'prog8c'
archiveVersion = version
// minimize()
}
+2 -1
View File
@@ -15,7 +15,7 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="module" module-name="codeOptimizers" />
@@ -24,5 +24,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>
+136 -30
View File
@@ -12,37 +12,23 @@ atari {
&uword COLCRS = 85
&ubyte ROWCRS = 84
romsub $F24A = getchar() -> ubyte @A
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2FD = waitkey() -> ubyte @A
extsub $F24A = getchar() -> ubyte @A
extsub $F2B0 = outchar(ubyte character @ A)
extsub $F2FD = waitkey() -> ubyte @A
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
const ubyte target = 8 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; TODO
%asm {{
sei
cld
clc
; TODO reset screen mode etc etc
clv
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the Atari
}}
}
const ubyte sizeof_bool = 1
const ubyte sizeof_byte = 1
const ubyte sizeof_ubyte = 1
const ubyte sizeof_word = 2
const ubyte sizeof_uword = 2
const ubyte sizeof_float = 0 ; undefined, no float support
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
@@ -154,6 +140,65 @@ _longcopy
}}
}
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
; Compares two blocks of memory
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
%asm {{
sta P8ZP_SCRATCH_REG ; lsb(size)
sty P8ZP_SCRATCH_B1 ; msb(size)
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldx P8ZP_SCRATCH_B1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_REG ; lsb(size)
bne +
rts
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_REG ; lsb(size)
bne -
lda #0
rts
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@@ -225,6 +270,7 @@ save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
; !notreached!
}}
}
@@ -248,27 +294,38 @@ save_SCRATCH_ZPWORD2 .word 0
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO where to store A as exit code?
%asm {{
sta p8_sys_startup.cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
; TODO where to store A,X,Y as exit code?
%asm {{
jmp exit
sta p8_sys_startup.cleanup_at_exit._exitcode
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
; TODO where to store A,X,Y,Carry as exit code?
%asm {{
jmp exit
sta p8_sys_startup.cleanup_at_exit._exitcode
lda #0
rol a
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp p8_sys_startup.cleanup_at_exit
}}
}
@@ -279,6 +336,13 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
@@ -427,6 +491,7 @@ cx16 {
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
; !notreached!
}}
}
@@ -447,3 +512,44 @@ cx16 {
}
}
p8_sys_startup {
; program startup and shutdown machinery. Needs to reside in normal system ram.
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; TODO
%asm {{
sei
; TODO reset screen mode etc etc
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
cld
clc
clv
rts
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
; experimental buffer data structures
; **experimental** buffer data structures, API subject to change!!
%option no_symbol_prefixing, ignore_unused
+4
View File
@@ -0,0 +1,4 @@
%import shared_cbm_diskio
; No alterations here; everything is taken from the shared module.
+285 -129
View File
@@ -56,51 +56,49 @@ cbm {
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.home or txt.plot
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
extsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
extsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
extsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
extsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
extsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
extsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
extsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
extsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
extsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
extsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
extsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
extsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
extsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
extsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
extsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
extsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
extsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
extsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
extsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
extsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
extsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
extsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
extsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
extsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
extsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
extsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
extsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
extsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
extsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
extsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
extsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
extsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 compatible ROM kernal routines ----
@@ -319,8 +317,27 @@ 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()
; ---- C128 specific system utility routines: ----
inline asmsub banks(ubyte banks @A) {
; -- set the memory bank configuration MMU register
%asm {{
sta $FF00
}}
}
inline asmsub getbanks() -> ubyte @A {
; -- get the current memory bank configuration from the MMU register
%asm {{
lda $FF00
}}
}
asmsub disable_basic() clobbers(A) {
%asm {{
lda $0a04 ; disable BASIC shadow registers
@@ -342,6 +359,56 @@ asmsub disable_basic() clobbers(A) {
}}
}
asmsub x16jsrfar() {
%asm {{
; setup a JSRFAR call (using X16 call convention)
; see https://cx16.dk/c128-kernal-routines/jsrfar.html
sty $08 ; save registers
stx $07
sta $06
php ; including PSR
pla
sta $05
pla ; get original return address
sta $fa ; and store it in a temp ZP pointer
pla
sta $fb
ldy #$01
lda ($fa),y ; grab low byte of target address
sta $04
iny
lda ($fa),y ; now the high byte
sta $03
iny
lda ($fa),y ; then the target bank
sta $02
; replace the original return address by it + 3 to skip the data bytes
clc
lda $fa
adc #3
sta $fa
lda $fb
adc #0
pha
lda $fa
pha
jsr c128.JSRFAR ; call kernal's jsrfar routine
lda $05 ; populate registers
pha
lda $06
ldx $07
ldy $08
plp
rts ; and return
}}
}
; ---- end of C128 specific system utility routines ----
}
@@ -349,82 +416,23 @@ asmsub disable_basic() clobbers(A) {
sys {
; ------- lowlevel system routines --------
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated.
%asm {{
sei
cld
lda #0
sta $ff00 ; select default bank 15
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
lda #6
sta c64.EXTCOL
lda #7
sta cbm.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
}
const ubyte sizeof_bool = 1
const ubyte sizeof_byte = 1
const ubyte sizeof_ubyte = 1
const ubyte sizeof_word = 2
const ubyte sizeof_uword = 2
const ubyte sizeof_float = 5
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C128
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
lda #0
sta $ff00 ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
sub disable_runstop_and_charsetswitch() {
p8_sys_startup.disable_runstop_and_charsetswitch()
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 247 ; disable charset switching
lda #112
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 247 ; enable charset switching
lda #110
sta 808 ; enable run/stop key
rts
}}
}
sub enable_runstop_and_charsetswitch() {
p8_sys_startup.enable_runstop_and_charsetswitch()
}
asmsub save_prog8_internals() {
%asm {{
@@ -445,6 +453,7 @@ save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
; !notreached!
}}
}
@@ -496,8 +505,6 @@ _modified
tax
pla
rti
_use_kernal .byte 0
}}
}
@@ -719,6 +726,65 @@ _longcopy
}}
}
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
; Compares two blocks of memory
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
%asm {{
sta P8ZP_SCRATCH_REG ; lsb(size)
sty P8ZP_SCRATCH_B1 ; msb(size)
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldx P8ZP_SCRATCH_B1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_REG ; lsb(size)
bne +
rts
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_REG ; lsb(size)
bne -
lda #0
rts
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@@ -780,37 +846,37 @@ _longcopy
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
sta cleanup_at_exit._exitcode
sta p8_sys_startup.cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
sta p8_sys_startup.cleanup_at_exit._exitcode
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
sta p8_sys_startup.cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
@@ -821,6 +887,13 @@ _longcopy
}}
}
inline asmsub progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
@@ -970,6 +1043,7 @@ cx16 {
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
; !notreached!
}}
}
@@ -990,3 +1064,85 @@ cx16 {
}
}
p8_sys_startup {
; program startup and shutdown machinery. Needs to reside in normal system ram.
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated.
%asm {{
sei
lda #0
sta $ff00 ; select default bank 15
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
lda #%00001110
sta $ff00 ; bank out basic rom so we have ram from $1c00-$bfff
lda #6
sta c64.EXTCOL
lda #7
sta cbm.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
cld
clc
clv
rts
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
lda #0
sta $ff00 ; default bank 15
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 247 ; disable charset switching
lda #112
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 247 ; enable charset switching
lda #110
sta 808 ; enable run/stop key
rts
}}
}
}
+3 -1
View File
@@ -13,7 +13,7 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
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() {
@@ -314,6 +314,7 @@ _mod sta $ffff ; modified
rts
_screenrows .word cbm.Screen + range(0, 1000, 40)
; !notreached!
}}
}
@@ -357,6 +358,7 @@ _mod sta $ffff ; modified
rts
_colorrows .word $d800 + range(0, 1000, 40)
; !notreached!
}}
}
+4
View File
@@ -0,0 +1,4 @@
%import shared_cbm_diskio
; No alterations here; everything is taken from the shared module.
+2 -6
View File
@@ -120,12 +120,8 @@ cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
.pend
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
; -- cast fac1 to word into A/Y
; clobbers X
jsr AYINT
ldy floats.AYINT_facmo
lda floats.AYINT_facmo+1
rts
; -- cast fac1 to word into A/Y. clobbers X
jmp AYINT2
.pend
+104 -52
View File
@@ -15,71 +15,85 @@ floats {
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
extsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
extsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
extsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
extsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
extsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
extsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
extsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
extsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
extsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
extsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
extsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY) DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD.
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
; there is also floats.FREAD{S,U}24AXY that read (un)signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
extsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
extsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
extsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
extsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse()
extsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
extsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
extsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
extsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
extsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
extsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
extsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
extsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
extsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
extsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
extsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
extsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
extsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
extsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
extsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
extsub $bc58 = ABS() ; fac1 = ABS(fac1)
extsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
extsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
extsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
extsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
extsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
extsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
extsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
extsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
extsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
extsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub AYINT2() clobbers(X) -> word @AY {
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
; (might throw ILLEGAL QUANTITY)
%asm {{
jsr AYINT
ldx #<floats_temp_var
ldy #>floats_temp_var
jsr MOVMF
lda floats_temp_var+4
ldy floats_temp_var+3
rts
}}
}
asmsub FREADS32() clobbers(A,X,Y) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
%asm {{
@@ -104,7 +118,6 @@ asmsub FREADUS32 () clobbers(A,X,Y) {
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
%asm {{
sty $62
stx $63
@@ -119,10 +132,43 @@ asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)
}}
}
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
%asm{{
FAC = $61
sty FAC+1
stx FAC+2
sta FAC+3
cpy #$00
bne +
cpx #$00
bne +
cmp #$00
beq ++
+ ldx #$98
bit FAC+1
bmi +
- dex
asl FAC+3
rol FAC+2
rol FAC+1
bpl -
+ stx FAC
lda #$00
sta FAC+4
sta FAC+5
rts
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
sty $62
sty $62 ; facmo
sta $63
ldx #$90
sec
@@ -151,7 +197,6 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
}}
}
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
sub rnd() -> float {
%asm {{
@@ -177,6 +222,13 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
}}
}
; get the jiffy clock as a float
asmsub time() -> float @ FAC1 {
%asm {{
jsr cbm.RDTIM
jmp floats.FREADU24AXY
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+24 -7
View File
@@ -288,17 +288,30 @@ hline_zero2
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Midpoint algorithm, filled.
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*2+1)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*2+1)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -307,6 +320,10 @@ hline_zero2
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*2+1)
horizontal_line(xcenter-yy, last_y4, yy*2+1)
}
@@ -330,12 +347,11 @@ hline_zero2
uword @zp internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
asmsub internal_plot(ubyte ploty @Y) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
lda p8v_internal_plotx+1
sta P8ZP_SCRATCH_W2+1
lsr a ; 0
lsr a ; make a=0
sta P8ZP_SCRATCH_W2
lda p8v_internal_plotx
pha
@@ -360,7 +376,7 @@ hline_zero2
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
; 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
; 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.
@@ -369,6 +385,7 @@ _plot_y_values := p8c_BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
; !notreached!
}}
}
+321 -131
View File
@@ -57,50 +57,50 @@ cbm {
; ---- CBM ROM kernal routines (C64 addresses) ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
extsub $AB1E = STROUT(uword 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
extsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
extsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
extsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
extsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
extsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
extsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
extsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
extsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
extsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
extsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
extsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
extsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
extsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
extsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
extsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
extsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
extsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
extsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
extsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
extsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
extsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
extsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
extsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
extsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
extsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
extsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
extsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
extsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
extsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
extsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
extsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
extsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
extsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
extsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
@@ -307,6 +307,93 @@ c64 {
; ---- end of SID registers ----
asmsub banks(ubyte banks @A) {
; -- set the memory bank configuration
; see https://www.c64-wiki.com/wiki/Bank_Switching
%asm {{
and #%00000111
sta P8ZP_SCRATCH_REG
sei
lda $01
and #%11111000
ora P8ZP_SCRATCH_REG
sta $01
cli
rts
}}
}
inline asmsub getbanks() -> ubyte @A {
; -- get the current memory bank configuration
; see https://www.c64-wiki.com/wiki/Bank_Switching
%asm {{
lda $01
and #%00000111
}}
}
asmsub x16jsrfar() {
%asm {{
; setup a JSRFAR call (using X16 call convention)
sta P8ZP_SCRATCH_W2 ; save A
sty P8ZP_SCRATCH_W2+1 ; save Y
php
pla
sta P8ZP_SCRATCH_REG ; save Status
pla
sta P8ZP_SCRATCH_W1
pla
sta P8ZP_SCRATCH_W1+1
; retrieve arguments
ldy #$01
lda (P8ZP_SCRATCH_W1),y ; grab low byte of target address
sta _jmpfar+1
iny
lda (P8ZP_SCRATCH_W1),y ; now the high byte
sta _jmpfar+2
iny
lda (P8ZP_SCRATCH_W1),y ; then the target bank
sta P8ZP_SCRATCH_B1
; adjust return address to skip over the arguments
clc
lda P8ZP_SCRATCH_W1
adc #3
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc #0
pha
lda P8ZP_SCRATCH_W1
pha
lda $01 ; save old ram banks
pha
; set target bank, restore A, Y and flags
lda P8ZP_SCRATCH_REG
pha
lda P8ZP_SCRATCH_B1
jsr banks
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
plp
jsr _jmpfar ; do the actual call
; restore bank without clobbering status flags and A register
sta P8ZP_SCRATCH_W1
php
pla
sta P8ZP_SCRATCH_B1
pla
jsr banks
lda P8ZP_SCRATCH_B1
pha
lda P8ZP_SCRATCH_W1
plp
rts
_jmpfar jmp $0000 ; modified
}}
}
sub get_vic_memory_base() -> uword {
; one of the 4 possible banks. $0000/$4000/$8000/$c000.
@@ -345,85 +432,22 @@ c64 {
sys {
; ------- lowlevel system routines --------
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
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
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated.
%asm {{
sei
cld
lda #%00101111
sta $00
lda #%00100111
sta $01
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
lda #6
sta c64.EXTCOL
lda #7
sta cbm.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
}
sub disable_runstop_and_charsetswitch() {
p8_sys_startup.disable_runstop_and_charsetswitch()
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C64
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
lda #31
sta $01 ; bank the kernal in
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 657 ; disable charset switching
lda #239
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 657 ; enable charset switching
lda #237
sta 808 ; enable run/stop key
rts
}}
}
sub enable_runstop_and_charsetswitch() {
p8_sys_startup.enable_runstop_and_charsetswitch()
}
asmsub save_prog8_internals() {
%asm {{
@@ -444,6 +468,7 @@ save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
; !notreached!
}}
}
@@ -717,6 +742,65 @@ _longcopy
}}
}
asmsub memcmp(uword address1 @R0, uword address2 @R1, uword size @AY) -> byte @A {
; Compares two blocks of memory
; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2.
%asm {{
sta P8ZP_SCRATCH_REG ; lsb(size)
sty P8ZP_SCRATCH_B1 ; msb(size)
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldx P8ZP_SCRATCH_B1
beq _no_msb_size
_loop_msb_size
ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dec P8ZP_SCRATCH_B1 ; msb(size) -= 1
dex
bne _loop_msb_size
_no_msb_size
lda P8ZP_SCRATCH_REG ; lsb(size)
bne +
rts
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bcs +
lda #-1
rts
+ beq +
lda #1
rts
+ iny
cpy P8ZP_SCRATCH_REG ; lsb(size)
bne -
lda #0
rts
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@@ -778,37 +862,37 @@ _longcopy
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
sta cleanup_at_exit._exitcode
sta p8_sys_startup.cleanup_at_exit._exitcode
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
; -- immediately exit the program with result values in the A, X and Y registers.
%asm {{
sta cleanup_at_exit._exitcode
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
sta p8_sys_startup.cleanup_at_exit._exitcode
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
%asm {{
sta cleanup_at_exit._exitcode
sta p8_sys_startup.cleanup_at_exit._exitcode
lda #0
rol a
sta cleanup_at_exit._exitcodeCarry
stx cleanup_at_exit._exitcodeX
sty cleanup_at_exit._exitcodeY
sta p8_sys_startup.cleanup_at_exit._exitcodeCarry
stx p8_sys_startup.cleanup_at_exit._exitcodeX
sty p8_sys_startup.cleanup_at_exit._exitcodeY
ldx prog8_lib.orig_stackpointer
txs
jmp cleanup_at_exit
jmp p8_sys_startup.cleanup_at_exit
}}
}
@@ -819,6 +903,13 @@ _longcopy
}}
}
inline asmsub progstart() -> uword @AY {
%asm {{
lda #<prog8_program_start
ldy #>prog8_program_start
}}
}
inline asmsub push(ubyte value @A) {
%asm {{
pha
@@ -968,6 +1059,7 @@ cx16 {
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
; !notreached!
}}
}
@@ -988,3 +1080,101 @@ cx16 {
}
}
p8_sys_startup {
; program startup and shutdown machinery. Needs to reside in normal system ram.
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the KERNAL and CHARGEN ROMs are banked in,
; BASIC ROM is NOT banked in (so we have another 8Kb of RAM at our disposal),
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated.
%asm {{
sei
lda #%00101111
sta $00
lda #%00100110 ; kernal and i/o banked in, basic off
sta $01
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
lda #6
sta c64.EXTCOL
lda #7
sta cbm.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
lda #PROG8_C64_BANK_CONFIG ; apply bank config
sta $01
and #1
bne +
; basic is not banked in, adjust MEMTOP
ldx #<$d000
ldy #>$d000
clc
jsr cbm.MEMTOP
+ cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
cld
clc
clv
rts
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
lda #%00101111
sta $00
lda #31
sta $01 ; bank the kernal and basic in
ldx #<$a000
ldy #>$a000
clc
jsr cbm.MEMTOP ; adjust MEMTOP down again
jsr cbm.CLRCHN ; reset i/o channels
jsr enable_runstop_and_charsetswitch
_exitcodeCarry = *+1
lda #0
lsr a
_exitcode = *+1
lda #0 ; exit code possibly modified in exit()
_exitcodeX = *+1
ldx #0
_exitcodeY = *+1
ldy #0
rts
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 657 ; disable charset switching
lda #239
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 657 ; enable charset switching
lda #237
sta 808 ; enable run/stop key
rts
}}
}
}
+3 -1
View File
@@ -13,7 +13,7 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
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)
@@ -317,6 +317,7 @@ _mod sta $ffff ; modified
rts
_screenrows .word cbm.Screen + range(0, 1000, 40)
; !notreached!
}}
}
@@ -360,6 +361,7 @@ _mod sta $ffff ; modified
rts
_colorrows .word $d800 + range(0, 1000, 40)
; !notreached!
}}
}
+409
View File
@@ -0,0 +1,409 @@
; **experimental** data compression/decompression routines, API subject to change!!
compression {
%option no_symbol_prefixing, ignore_unused
sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) {
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
; output_function = address of a routine that gets a byte arg in A,
; this is the next RLE byte to write to the compressed output buffer or file.
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
; compressed blocks (for instance if the source data is >64Kb)
; Worst case result storage size needed = (size + (size+126) / 127) + 1
; This routine is not optimized for speed but for readability and ease of use.
uword idx = 0
uword literals_start_idx = 0
ubyte literals_length = 0
asmsub call_output_function(ubyte arg @A) {
%asm {{
jmp (p8v_output_function)
}}
}
sub next_same_span() {
; returns length in cx16.r1L, and the byte value in cx16.r1H
cx16.r1H = data[idx]
cx16.r1L = 0
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
idx++
cx16.r1L++
}
}
sub output_literals() {
call_output_function(literals_length-1)
uword dataptr = data + literals_start_idx
ubyte i
for i in 0 to literals_length-1 {
call_output_function(@(dataptr))
dataptr++
}
literals_length = 0
}
while idx<size {
next_same_span() ; count in r1L, value in r1H
if cx16.r1L>1 {
; a replicate run
if literals_length>0
output_literals()
call_output_function((cx16.r1L^255)+2) ; 257-cx16.r1L
call_output_function(cx16.r1H)
}
else {
; add more to the literals run
if literals_length==128
output_literals()
if literals_length==0
literals_start_idx = idx-1
literals_length++
}
}
if literals_length>0
output_literals()
if is_last_block
call_output_function(128)
}
sub encode_rle(uword data, uword size, uword target, bool is_last_block) -> uword {
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
; Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1.
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
; compressed blocks (for instance if the source data is >64Kb)
; This routine is not optimized for speed but for readability and ease of use.
uword idx = 0
uword literals_start_idx = 0
ubyte literals_length = 0
uword orig_target = target
sub next_same_span() {
; returns length in cx16.r1L, and the byte value in cx16.r1H
cx16.r1H = data[idx]
cx16.r1L = 0
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
idx++
cx16.r1L++
}
}
sub output_literals() {
@(target) = literals_length-1
target++
uword dataptr = data + literals_start_idx
ubyte i
for i in 0 to literals_length-1 {
@(target) = @(dataptr)
target++
dataptr++
}
literals_length = 0
}
while idx<size {
next_same_span() ; count in r1L, value in r1H
if cx16.r1L>1 {
; a replicate run
if literals_length>0
output_literals()
@(target) = (cx16.r1L^255)+2 ; 257-cx16.r1L
target++
@(target) = cx16.r1H
target++
}
else {
; add more to the literals run
if literals_length==128
output_literals()
if literals_length==0
literals_start_idx = idx-1
literals_length++
}
}
if literals_length>0
output_literals()
if is_last_block {
@(target) = 128
target ++
}
return target-orig_target
}
asmsub decode_rle_srcfunc(uword source_function @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
; instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
; Stops decompressing when the maxsize has been reached.
%asm {{
sta _cb_mod1+1
sty _cb_mod1+2
sta _cb_mod2+1
sty _cb_mod2+2
sta _cb_mod3+1
sty _cb_mod3+2
lda cx16.r0L
ldy cx16.r0H
sta P8ZP_SCRATCH_W2 ; target ptr
sta _orig_target
sty P8ZP_SCRATCH_W2+1
sty _orig_target+1
lda cx16.r0L
clc
adc cx16.r1L
sta cx16.r1L
lda cx16.r0H
adc cx16.r1H
sta cx16.r1H ; decompression limit
_loop
; while target (W2) < limit (r1)
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
cmp cx16.r1L
tya
sbc cx16.r1H
bcs _end
_cb_mod1 jsr $ffff ; modified
bpl _literals
cmp #128
beq _end
; replicate next byte -n+1 times
eor #255
clc
adc #2
sta P8ZP_SCRATCH_REG
_cb_mod2 jsr $ffff ; modified
ldx P8ZP_SCRATCH_REG
ldy #0
- sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
; add A to target
lda P8ZP_SCRATCH_REG
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_literals
; copy the next n+1 bytes
pha
sta P8ZP_SCRATCH_B1
ldy #0
sty P8ZP_SCRATCH_REG
_cb_mod3 jsr $ffff ; modified
ldy P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W2),y
inc P8ZP_SCRATCH_REG
dec P8ZP_SCRATCH_B1
bpl _cb_mod3
; add N+1 to target
pla
sec
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_orig_target .word 0
_end
; return w2-orig_target, the size of the decompressed data
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sec
sbc _orig_target
tax
tya
sbc _orig_target+1
tay
txa
rts
}}
}
asmsub decode_rle(uword compressed @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
; Stops decompressing when the maxsize has been reached.
%asm {{
sta P8ZP_SCRATCH_W1 ; compressed data ptr
sty P8ZP_SCRATCH_W1+1
lda cx16.r0L
ldy cx16.r0H
sta P8ZP_SCRATCH_W2 ; target ptr
sta _orig_target
sty P8ZP_SCRATCH_W2+1
sty _orig_target+1
lda cx16.r0L
clc
adc cx16.r1L
sta cx16.r1L
lda cx16.r0H
adc cx16.r1H
sta cx16.r1H ; decompression limit
_loop ; while target (W2) < limit (r1)
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
cmp cx16.r1L
tya
sbc cx16.r1H
bcs _end
ldy #0
lda (P8ZP_SCRATCH_W1),y
bpl _literals
cmp #128
beq _end
; replicate next byte -n+1 times
eor #255
clc
adc #2
pha
tax
iny
lda (P8ZP_SCRATCH_W1),y
ldy #0
- sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
; add A to target
pla
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
; increase source by 2
clc
lda P8ZP_SCRATCH_W1
adc #2
sta P8ZP_SCRATCH_W1
lda #0
adc P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1+1
jmp _loop
_literals
; copy the next n+1 bytes
pha
tax
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
dex
bpl -
; add N+1 to source
pla
tax
sec
adc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
lda #0
adc P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1+1
; add N+1 to target as well
txa
sec
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
jmp _loop
_orig_target .word 0
_end
; return w2-orig_target, the size of the decompressed data
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sec
sbc _orig_target
tax
tya
sbc _orig_target+1
tay
txa
rts
}}
}
/***
; prog8 source code for the asm routine above:
sub decode_rle_prog8(uword @zp compressed, uword @zp target, uword maxsize) -> uword {
cx16.r0 = target ; original target
cx16.r1 = target+maxsize ; decompression limit
while target<cx16.r1 {
cx16.r2L = @(compressed)
if_neg {
if cx16.r2L==128
break
; replicate the next byte -n+1 times
compressed++
cx16.r3L = @(compressed)
repeat 2+(cx16.r2L^255) {
@(target) = cx16.r3L
target++
}
compressed++
} else {
; copy the next n+1 bytes
compressed++
repeat cx16.r2L+1 {
@(target) = @(compressed)
compressed++
target++
}
}
}
return target-cx16.r0
}
sub decode_rle_callback_prog8(uword producer_callback, uword @zp target, uword maxsize) -> uword {
cx16.r0 = target ; original target
cx16.r1 = target+maxsize ; decompression limit
while target<cx16.r1 {
cx16.r2L = lsb(call(producer_callback))
if_neg {
if cx16.r2L==128
break
; replicate the next byte -n+1 times
cx16.r3L = lsb(call(producer_callback))
repeat 2+(cx16.r2L^255) {
@(target) = cx16.r3L
target++
}
} else {
; copy the next n+1 bytes
repeat cx16.r2L+1 {
@(target) = lsb(call(producer_callback))
target++
}
}
}
return target-cx16.r0
}
***/
}
+6 -1
View File
@@ -200,6 +200,7 @@ _allzero lda #'0'
sta string_out,x
inx
bne _end
; !notreached!
}}
}
@@ -402,6 +403,7 @@ _digit
bne _parse
; never reached
_negative .byte 0
; !notreached!
}}
}
@@ -472,6 +474,7 @@ _try_iso
bcs _stop
and #63
bne _add_letter
; !notreached!
}}
}
@@ -702,7 +705,7 @@ decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
; !notreached!
}}
}
@@ -736,6 +739,7 @@ asmsub internal_ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
; !notreached!
}}
}
@@ -753,6 +757,7 @@ asmsub internal_uword2hex (uword value @AY) clobbers(A,Y) {
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
; !notreached!
}}
}
+1
View File
@@ -1,4 +1,5 @@
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
; Bitmap data is loaded directly into VRAM without intermediary buffering.
; Only uncompressed images are supported for now.
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
+35 -13
View File
@@ -145,6 +145,7 @@ io_error:
uword list_pattern
uword list_blocks
bool iteration_in_progress = false
bool write_iteration_in_progress = false
str list_filetype = "???" ; prg, seq, dir
str list_filename = "?" * 50
@@ -423,6 +424,7 @@ _end jsr cbm.READST
sub f_close() {
; -- end an iterative file loading session (close channels).
; it is safe to call this multiple times, or when no file is open for reading.
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(READ_IO_CHANNEL)
@@ -460,7 +462,10 @@ _end jsr cbm.READST
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
void cbm.OPEN() ; open 13,8,13,"filename"
if_cc {
return cbm.READST()==0
if cbm.READST()==0 {
write_iteration_in_progress = true
return true
}
}
cbm.CLOSE(WRITE_IO_CHANNEL)
f_close_w()
@@ -498,8 +503,12 @@ no_mciout:
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(WRITE_IO_CHANNEL)
; it is safe to call this multiple times, or when no file is open for reading.
if write_iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(WRITE_IO_CHANNEL)
write_iteration_in_progress = false
}
}
@@ -703,7 +712,7 @@ io_error:
; CommanderX16 extensions over the basic C64/C128 diskio routines:
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
; Calculates the number of bytes loaded (files > 64Kb are truncated to 16 bits)
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
@@ -757,6 +766,11 @@ internal_vload:
}}
}
; note: There is no vsave_raw() routine because the Kernal doesn't have a VSAVE routine.
; You'll have to write your own loop that reads vram data and use
; cbm.CHROUT or cx16.MCIOUT to write it to an open output file.
sub chdir(str path) {
; -- change current directory.
list_filename[0] = 'c'
@@ -864,6 +878,15 @@ io_error:
send_command(list_filename)
}
sub exists(str filename) -> bool {
; -- returns true if the given file exists on the disk, otherwise false
if f_open(filename) {
f_close()
return true
}
return false
}
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
@@ -935,15 +958,14 @@ io_error:
return
cx16.r0 = cx16.r1 = cx16.r2 = cx16.r3 = 0
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
}
}
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
}
}
+21
View File
@@ -62,4 +62,25 @@ emudbg {
if is_emulator()
EMU_DBGOUT2 = value
}
sub reset_cpu_cycles() {
if is_emulator()
EMU_CPUCLK_L = 0
}
asmsub cpu_cycles() -> uword @R0, uword @R1 {
; -- returns the 32 bits cpu clock counter in R1:R0,
; returns lower 16 bits in R0, upper 16 bits in R1.
%asm {{
lda p8v_EMU_CPUCLK_L
sta cx16.r0L
lda p8v_EMU_CPUCLK_M
sta cx16.r0H
lda p8v_EMU_CPUCLK_H
sta cx16.r1L
lda p8v_EMU_CPUCLK_U
sta cx16.r1H
rts
}}
}
}
+106 -52
View File
@@ -14,69 +14,82 @@ floats {
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. (might throw ILLEGAL QUANTITY) See "basic.sym" kernal symbol file for their memory locations.
extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. DON'T USE THIS, USE WRAPPER 'AYINT2' INSTEAD. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
extsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $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) -> 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.
; 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)
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
extsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
extsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
romsub $fe12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $fe15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $fe18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
romsub $fe39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
romsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
extsub $fe12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
extsub $fe15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
extsub $fe18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
extsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
extsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
extsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
extsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
extsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
extsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
extsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
extsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
extsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
extsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
extsub $fe39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
extsub $fe3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
extsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
extsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
extsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
extsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
extsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
extsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
extsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
extsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
extsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
extsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
extsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
extsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
extsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
extsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
extsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
extsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
extsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
; X16 additions
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
extsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
extsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
extsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
extsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
extsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
extsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
extsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
extsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
extsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
extsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
extsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
extsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
asmsub AYINT2() clobbers(X) -> word @AY {
; fac1-> signed word in AY. Safe wrapper around the AYINT kernal routine (not reading internal memory locations)
; (might throw ILLEGAL QUANTITY)
%asm {{
jsr AYINT
ldx #<floats_temp_var
ldy #>floats_temp_var
jsr MOVMF
lda floats_temp_var+4
ldy floats_temp_var+3
rts
}}
}
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
; ---- 8 bit signed A -> float in fac1
@@ -90,8 +103,42 @@ asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
}}
}
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
%asm{{
FAC = $C3
sty FAC+1
stx FAC+2
sta FAC+3
cpy #$00
bne +
cpx #$00
bne +
cmp #$00
beq ++
+ ldx #$98
bit FAC+1
bmi +
- dex
asl FAC+3
rol FAC+2
rol FAC+1
bpl -
+ stx FAC
stz FAC+4
stz FAC+5
rts
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
; See "basic.sym" kernal symbol file for the facmo memory location.
; TODO find a way to not depend on that internal fac memory location. MOVFM doesn't work.
%asm {{
sty $c4 ; facmo
sta $c5 ; facmo+1
@@ -154,12 +201,11 @@ _borked
+ jmp sys.exit
_msg .text 13,"?rom 47+ required for val1",13,0
; !notreached!
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT (See "basic.sym" kernal symbol file)
sub rnd() -> float {
%asm {{
lda #1
@@ -173,6 +219,14 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
}}
}
; get the jiffy clock as a float
asmsub time() -> float @ FAC1 {
%asm {{
jsr cbm.RDTIM
jmp floats.FREADU24AXY
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
@@ -1,114 +1,49 @@
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; Custom routines to use the full-screen 640x480 in 4 color mode.
; (monochrome mode can be used via the monogfx module)
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for identical routines for a monochrome 1 bpp screen, use the "monogfx" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
; NOTE: the bitmap screen data is positioned in vram at $00000
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
;
; NOTE: the bitmap screen data is positioned in vram at $0:0000
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
; mode 2 = bitmap 640 x 480 x 4c (2 bpp)
; mode 3 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; mode 4 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
; mode 5 = bitmap 640 x 400 x 16c (not yet implemented)
gfx2 {
gfx_hires {
%option ignore_unused
; read-only control variables:
ubyte active_mode = 0
uword width = 0
uword height = 0
ubyte bpp = 0
const uword WIDTH = 640
const uword HEIGHT = 480
sub screen_mode(ubyte mode) {
sub graphics_mode() {
; set hires 4c mode
cx16.VERA_CTRL=0
when mode {
1 -> {
; lores 256c
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 = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
}
2 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
}
else -> {
; back to default text mode
if active_mode!=0 {
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
}
}
init_mode(mode)
if active_mode!=0
clear_screen(0)
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
clear_screen(0)
}
sub init_mode(ubyte mode) {
; set the internal configuration variables corresponding to the given screenmode
; doesn't manipulate Vera / the actual display mode
active_mode = mode
when mode {
1 -> {
width = 320
height = 240
bpp = 8
}
2 -> {
width = 640
height = 480
bpp = 2
}
else -> {
width = 0
height = 0
bpp = 0
active_mode = 0
}
}
sub text_mode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
sub clear_screen(ubyte color) {
position(0, 0)
when active_mode {
1 -> {
; lores 256c
repeat 240/2
cs_innerloop640(color)
}
2 -> {
; highres 4c
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
color = colors[color&3]
repeat 480/4
cs_innerloop640(color)
}
}
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
color = colors[color&3]
repeat 480/4
cs_innerloop640(color)
position(0, 0)
}
@@ -125,6 +60,18 @@ gfx2 {
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
}
sub safe_rect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
; does bounds checking and clipping
safe_horizontal_line(xx, yy, rwidth, color)
if rheight==1
return
safe_horizontal_line(xx, yy+rheight-1, rwidth, color)
safe_vertical_line(xx, yy+1, rheight-2, color)
if rwidth==1
return
safe_vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
}
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
@@ -136,140 +83,124 @@ gfx2 {
}
}
sub safe_fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
; This safe version does bounds checking and clipping.
if xx>=WIDTH or yy>=WIDTH
return
if msb(xx)&$80!=0 {
rwidth += xx
xx = 0
}
if xx>=WIDTH
return
if xx+rwidth>WIDTH
rwidth = WIDTH-xx
if rwidth>WIDTH
return
if yy + rheight > HEIGHT
rheight = HEIGHT-yy
if rheight>HEIGHT
return
repeat rheight {
horizontal_line(xx, yy, rwidth, color)
yy++
}
}
sub horizontal_line(uword xx, uword yy, uword length, ubyte color) {
if length==0
return
when active_mode {
1 -> {
; lores 256c
position(xx, yy)
%asm {{
lda p8v_color
ldx p8v_length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy p8v_length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
2 -> {
; highres 4c ....also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
ldx p8v_xx
}}
; highres 4c ....also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
ldx p8v_xx
}}
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and p8b_gfx2.p8s_plot.p8v_mask4c,y
ora p8v_colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne ++
inc cx16.VERA_ADDR_L
bne ++
inc cx16.VERA_ADDR_M
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and p8b_gfx_hires.p8s_plot.p8v_mask4c,y
ora p8v_colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne ++
inc cx16.VERA_ADDR_L
bne ++
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
inc cx16.VERA_ADDR_H
+ inx ; next pixel
}}
}
}
}}
}
}
sub safe_horizontal_line(uword xx, uword yy, uword length, ubyte color) {
; does bounds checking and clipping
if msb(yy)&$80!=0 or yy>=height
if msb(yy)&$80!=0 or yy>=HEIGHT
return
if msb(xx)&$80!=0 {
length += xx
xx = 0
}
if xx>=width
if xx>=WIDTH
return
if xx+length>width
length = width-xx
if length>width
if xx+length>WIDTH
length = WIDTH-xx
if length>WIDTH
return
horizontal_line(xx, yy, length, color)
}
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
when active_mode {
1 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(xx,yy)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy p8v_lheight
beq +
lda p8v_color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
2 -> {
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if lheight==0
return
position2(xx,yy,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
;; color &= 3
;; color <<= gfx2.plot.shift4c[lsb(xx) & 3]
cx16.r2L = lsb(xx) & 3
when color & 3 {
0 -> color = 0
1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
}
ubyte @shared mask = gfx2.plot.mask4c[lsb(xx) & 3]
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and p8v_mask
ora p8v_color
sta cx16.VERA_DATA1
}}
}
}
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if lheight==0
return
position2(xx,yy,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
;; color &= 3
;; color <<= plot.shift4c[lsb(xx) & 3]
cx16.r2L = lsb(xx) & 3
when color & 3 {
0 -> color = 0
1 -> color = gfx_hires.plot.shiftedleft_4c_1[cx16.r2L]
2 -> color = gfx_hires.plot.shiftedleft_4c_2[cx16.r2L]
3 -> color = gfx_hires.plot.shiftedleft_4c_3[cx16.r2L]
}
ubyte @shared mask = gfx_hires.plot.mask4c[lsb(xx) & 3]
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and p8v_mask
ora p8v_color
sta cx16.VERA_DATA1
}}
}
sub set_both_strides(ubyte stride) {
@@ -282,9 +213,30 @@ gfx2 {
}
sub safe_vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
; does bounds checking and clipping
if msb(yy)&$80!=0 or yy>=HEIGHT
return
if msb(xx)&$80!=0 or xx>=WIDTH
return
if msb(yy)&$80!=0 {
lheight += yy
yy = 0
}
if yy+lheight>HEIGHT
lheight = HEIGHT-yy
if lheight>HEIGHT
return
vertical_line(xx, yy, lheight, color)
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; NOTE: if you are using lores 256c (mode 1), the line routine in the fastline module can be used and is MUCH faster than this one.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
@@ -479,12 +431,25 @@ gfx2 {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -493,6 +458,11 @@ gfx2 {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -502,12 +472,23 @@ gfx2 {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
@@ -516,6 +497,10 @@ gfx2 {
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
safe_horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
@@ -525,107 +510,68 @@ gfx2 {
ubyte[4] shiftedleft_4c_2 = [2<<6, 2<<4, 2<<2, 2<<0]
ubyte[4] shiftedleft_4c_3 = [3<<6, 3<<4, 3<<2, 3<<0]
when active_mode {
1 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda p8v_color
sta cx16.VERA_DATA0
}}
}
2 -> {
; highres 4c ....also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(xx) & 3 ; xbits
; color &= 3
; color <<= shift4c[cx16.r2L]
when color & 3 {
0 -> color = 0
1 -> color = shiftedleft_4c_1[cx16.r2L]
2 -> color = shiftedleft_4c_2[cx16.r2L]
3 -> color = shiftedleft_4c_3[cx16.r2L]
}
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
ldy cx16.r2L ; xbits
lda p8v_mask4c,y
and cx16.VERA_DATA0
ora p8v_color
sta cx16.VERA_DATA0
}}
}
; highres 4c ....also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(xx) & 3 ; xbits
; color &= 3
; color <<= shift4c[cx16.r2L]
when color & 3 {
0 -> color = 0
1 -> color = shiftedleft_4c_1[cx16.r2L]
2 -> color = shiftedleft_4c_2[cx16.r2L]
3 -> color = shiftedleft_4c_3[cx16.r2L]
}
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
ldy cx16.r2L ; xbits
lda p8v_mask4c,y
and cx16.VERA_DATA0
ora p8v_color
sta cx16.VERA_DATA0
}}
}
sub safe_plot(uword xx, uword yy, ubyte color) {
; A plot that does bounds checks to see if the pixel is inside the screen.
if msb(xx)&$80!=0 or msb(yy)&$80!=0
return
if xx >= width or yy >= height
if xx >= WIDTH or yy >= HEIGHT
return
plot(xx, yy, color)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
when active_mode {
1 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
}}
}
2 -> {
; hires 4c
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
pha
lda p8v_xx
and #3
tay
lda p8b_gfx2.p8s_plot.p8v_shift4c,y
tay
pla
cpy #0
beq +
- lsr a
dey
bne -
+ and #3
}}
}
else -> return 0
}
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
pha
lda p8v_xx
and #3
tay
lda p8b_gfx_hires.p8s_plot.p8v_shift4c,y
tay
pla
cpy #0
beq +
- lsr a
dey
bne -
+ and #3
rts
}}
}
sub fill(uword x, uword y, ubyte new_color) {
@@ -657,7 +603,7 @@ gfx2 {
if cx16.r12L==MAXDEPTH
return
cx16.r0s = sy+sdy
if cx16.r0s>=0 and cx16.r0s<=height-1 {
if cx16.r0s>=0 and cx16.r0s<=HEIGHT-1 {
;; stack_xl[cx16.r12L] = sxl
;; stack_xr[cx16.r12L] = sxr
;; stack_y[cx16.r12L] = sy
@@ -714,29 +660,23 @@ gfx2 {
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
if xx<0 or xx>WIDTH-1 or yy<0 or yy>HEIGHT-1
return
if gfx2.active_mode == 2 set_color_masks()
set_color_masks()
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L!=0 {
pop_stack()
xx = x1
when active_mode {
1 -> if fill_scanline_left_8bpp() goto skip
2 -> if fill_scanline_left_2bpp() goto skip
}
if fill_scanline_left_2bpp() goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
when active_mode {
1 -> fill_scanline_right_8bpp()
2 -> fill_scanline_right_2bpp()
}
fill_scanline_right_2bpp()
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
@@ -759,37 +699,12 @@ skip:
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_ADDR = cx16.r0
}
sub fill_scanline_left_8bpp() -> bool {
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r1L |= %00011000 ; auto decrement enabled
set_vera_address()
cx16.r9s = xx
while xx >= 0 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx--
}
return xx==cx16.r9s
}
sub fill_scanline_right_8bpp() {
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r1L |= %00010000 ; auto increment enabled
set_vera_address()
while xx <= width-1 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx++
}
cx16.VERA_CTRL = 0
}
sub fill_scanline_left_2bpp() -> bool {
uword vx = xx as uword
void gfx2.addr_mul_24_for_highres_4c(yy as uword,vx)
void gfx_hires.addr_mul_24_for_highres_4c(yy as uword,vx)
cx16.r1L |= %0001_1000 ; auto decrement
set_vera_address()
cmask = amask[lsb(vx) & 3] ; set the color mask for the first color pel
@@ -833,7 +748,7 @@ set_byte:
}
sub fill_scanline_right_2bpp() {
void gfx2.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
void gfx_hires.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
cx16.r1L |= %00010000 ; auto increment
set_vera_address()
cmask = amask[lsb(xx) & 3] ; set the color mask for the first color pel
@@ -867,7 +782,7 @@ set_byte:
}
set_byte:
cx16.VERA_DATA1 = nvub
if xx >= gfx2.width-1 break
if xx >= WIDTH-1 break
cmask = $C0
}
}
@@ -879,16 +794,7 @@ set_byte:
}
sub position(uword @zp xx, uword yy) {
when active_mode {
1 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
}
2 -> {
; highres 4c
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
}
}
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
@@ -970,83 +876,36 @@ set_byte:
ubyte[8] @shared char_bitmap_bytes_left
ubyte[8] @shared char_bitmap_bytes_right
when active_mode {
1 -> {
; lores 256c
while @(sctextptr)!=0 {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
position(xx,yy)
yy++
%asm {{
ldx p8v_color
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8
- asl P8ZP_SCRATCH_B1
bcc +
stx cx16.VERA_DATA0 ; write a pixel
bra ++
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
+ dey
bne -
}}
}
xx+=8
yy-=8
sctextptr++
}
}
2 -> {
; hires 4c
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
cx16.r11L = color
while @(sctextptr)!=0 {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1) ; for reading the chardata from Vera data channel 1
position(xx, yy) ; only calculated once, we update vera address in the loop instead
cx16.VERA_ADDR_H &= $0f ; no auto increment
repeat 8 {
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
cx16.r7 = xx
repeat 8 {
cx16.r10L <<= 1
if_cs {
cx16.r2L = cx16.r7L & 3 ; xbits
when cx16.r11L & 3 {
1 -> cx16.r12L = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
2 -> cx16.r12L = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
3 -> cx16.r12L = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
else -> cx16.r12L = 0
}
cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx2.plot.mask4c[cx16.r2L] | cx16.r12L
}
cx16.r7++
if (cx16.r7 & 3) == 0 {
; increment the pixel address by one
%asm {{
stz cx16.VERA_CTRL
clc
lda cx16.VERA_ADDR_L
adc #1
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
lda cx16.VERA_ADDR_H
adc #0
sta cx16.VERA_ADDR_H
}}
}
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
cx16.r11L = color
while @(sctextptr)!=0 {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1) ; for reading the chardata from Vera data channel 1
position(xx, yy) ; only calculated once, we update vera address in the loop instead
cx16.VERA_ADDR_H &= $0f ; no auto increment
repeat 8 {
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
cx16.r7 = xx
repeat 8 {
cx16.r10L <<= 1
if_cs {
cx16.r2L = cx16.r7L & 3 ; xbits
when cx16.r11L & 3 {
1 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_1[cx16.r2L]
2 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_2[cx16.r2L]
3 -> cx16.r12L = gfx_hires.plot.shiftedleft_4c_3[cx16.r2L]
else -> cx16.r12L = 0
}
; increment pixel address to the next line
cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx_hires.plot.mask4c[cx16.r2L] | cx16.r12L
}
cx16.r7++
if (cx16.r7 & 3) == 0 {
; increment the pixel address by one
%asm {{
stz cx16.VERA_CTRL
clc
lda cx16.VERA_ADDR_L
adc #(640-8)/4
adc #1
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
@@ -1056,10 +915,25 @@ set_byte:
sta cx16.VERA_ADDR_H
}}
}
xx+=8
sctextptr++
}
; increment pixel address to the next line
%asm {{
stz cx16.VERA_CTRL
clc
lda cx16.VERA_ADDR_L
adc #(640-8)/4
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
lda cx16.VERA_ADDR_H
adc #0
sta cx16.VERA_ADDR_H
}}
}
xx+=8
sctextptr++
}
}
@@ -1130,50 +1004,4 @@ set_byte:
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
}
+901
View File
@@ -0,0 +1,901 @@
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
; bitmap image needs to start at VRAM addres $00000.
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
%import syslib
%import verafx
gfx_lores {
%option ignore_unused
const uword WIDTH = 320
const ubyte HEIGHT = 240
sub graphics_mode() {
; enable 320x240 256c bitmap graphics mode
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 = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
clear_screen(0)
drawmode_eor(false)
}
sub text_mode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
sub drawmode_eor(bool enabled) {
; with EOR drawing mode you can have non destructive drawing (2*EOR=restore original)
eor_mode = enabled
}
bool eor_mode
sub clear_screen(ubyte color) {
if verafx.available() {
; use verafx cache writes to quicly clear the screen
const ubyte vbank = 0
const uword vaddr = 0
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
cx16.VERA_ADDR_M = msb(vaddr)
cx16.VERA_ADDR_L = lsb(vaddr)
cx16.VERA_CTRL = 6<<1 ; dcsel = 6, fill the 32 bits cache
cx16.VERA_FX_CACHE_L = color
cx16.VERA_FX_CACHE_M = color
cx16.VERA_FX_CACHE_H = color
cx16.VERA_FX_CACHE_U = color
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
cx16.VERA_FX_MULT = 0
cx16.VERA_FX_CTRL = %01000000 ; cache write enable
repeat 320/4/4 {
%asm {{
ldy #240
- stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
dey
bne -
}}
}
cx16.VERA_FX_CTRL = 0 ; cache write disable
cx16.VERA_CTRL = 0
return
}
; fallback to cpu clear
cx16.VERA_CTRL=0
cx16.VERA_ADDR=0
cx16.VERA_ADDR_H = 1<<4 ; 1 pixel auto increment
repeat HEIGHT {
%asm {{
lda p8v_color
ldy #p8c_WIDTH/8
- .rept 8
sta cx16.VERA_DATA0
.endrept
dey
bne -
}}
}
cx16.VERA_ADDR=0
cx16.VERA_ADDR_H = 0
}
sub rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
if rwidth==0 or rheight==0
return
horizontal_line(xx, yy, rwidth, color)
if rheight==1
return
horizontal_line(xx, yy+rheight-1, rwidth, color)
vertical_line(xx, yy+1, rheight-2, color)
if rwidth==1
return
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
}
sub safe_rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
; does bounds checking and clipping
safe_horizontal_line(xx, yy, rwidth, color)
if rheight==1
return
uword bottomyy = yy as uword + rheight -1
if bottomyy<HEIGHT
safe_horizontal_line(xx, lsb(bottomyy), rwidth, color)
safe_vertical_line(xx, yy+1, rheight-2, color)
if rwidth==1
return
safe_vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
}
sub fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
if rwidth==0
return
repeat rheight {
horizontal_line(xx, yy, rwidth, color)
yy++
}
}
sub safe_fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
; This safe version does bounds checking and clipping.
if xx>=WIDTH or yy>=HEIGHT
return
if msb(xx)&$80!=0 {
rwidth += xx
xx = 0
}
if xx>=WIDTH
return
if xx+rwidth>WIDTH
rwidth = WIDTH-xx
if rwidth>WIDTH
return
if yy as uword + rheight > HEIGHT
rheight = HEIGHT-yy
if rheight>HEIGHT
return
repeat rheight {
horizontal_line(xx, yy, rwidth, color)
yy++
}
}
sub horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
if length==0
return
position(xx, yy)
; set vera auto-increment to 1 pixel
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (1<<4)
if eor_mode {
cx16.vaddr_clone(0) ; also setup port 1, for reading
%asm {{
ldx p8v_length+1
beq +
ldy #0
- lda p8v_color
eor cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy p8v_length ; remaining
beq +
- lda p8v_color
eor cx16.VERA_DATA1
sta cx16.VERA_DATA0
dey
bne -
+
}}
} else {
%asm {{
lda p8v_color
ldx p8v_length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy p8v_length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
}
sub safe_horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
; does bounds checking and clipping
if yy>=HEIGHT
return
if msb(xx)&$80!=0 {
length += xx
xx = 0
}
if xx>=WIDTH
return
if xx+length>WIDTH
length = WIDTH-xx
if length>WIDTH
return
horizontal_line(xx, yy, length, color)
}
sub vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
if lheight==0
return
position(xx, yy)
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
if eor_mode {
cx16.vaddr_clone(0) ; also setup port 1, for reading
%asm {{
ldy p8v_lheight
beq +
- lda p8v_color
eor cx16.VERA_DATA1
sta cx16.VERA_DATA0
dey
bne -
+
}}
} else {
%asm {{
ldy p8v_lheight
lda p8v_color
- sta cx16.VERA_DATA0
dey
bne -
}}
}
}
sub safe_vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
; does bounds checking and clipping
if yy>=HEIGHT
return
if msb(xx)&$80!=0 or xx>=WIDTH
return
if yy as uword + lheight > HEIGHT
lheight = HEIGHT-yy
if lheight>HEIGHT
return
vertical_line(xx, yy, lheight, color)
}
sub line(uword x1, ubyte y1, uword x2, ubyte y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; NOTE: this is about twice as fast as the kernal routine GRAPH_draw_line
; it trades memory for speed (uses inline plot routine and multiplication lookup tables)
;
; NOTE: is currently still a regular 6502 routine, could likely be made much faster with the VeraFX line helper.
cx16.r3L = y2 ; ensure zeropage
cx16.r1L = y1 ; ensure zeropage
if cx16.r1L > cx16.r3L {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
x1 = x2
x2 = cx16.r0
cx16.r0L = cx16.r1L
cx16.r1L = cx16.r3L
cx16.r3L = cx16.r0L
}
word @zp dx = x2 as word
word @zp dy = cx16.r3L
dx -= x1
dy -= cx16.r1L
if dx==0 {
vertical_line(x1, cx16.r1L, lsb(dy)+1, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, cx16.r1L, abs(dx) as uword +1, color)
return
}
word @zp d = 0
bool positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r0 = x1 ; ensure zeropage
cx16.r2 = x2 ; ensure zeropage
cx16.VERA_CTRL = 0
if dx >= dy {
if positive_ix {
repeat {
plot()
if cx16.r0==cx16.r2
return
cx16.r0++
d += dy2
if d > dx {
cx16.r1L++
d -= dx2
}
}
} else {
repeat {
plot()
if cx16.r0==cx16.r2
return
cx16.r0--
d += dy2
if d > dx {
cx16.r1L++
d -= dx2
}
}
}
}
else {
if positive_ix {
repeat {
plot()
if cx16.r1L == cx16.r3L
return
cx16.r1L++
d += dx2
if d > dy {
cx16.r0++
d -= dy2
}
}
} else {
repeat {
plot()
if cx16.r1L == cx16.r3L
return
cx16.r1L++
d += dx2
if d > dy {
cx16.r0--
d -= dy2
}
}
}
}
asmsub plot() {
; internal plot routine for the line algorithm: x in r0, y in r1, color in variable.
%asm {{
ldy cx16.r1L
clc
lda times320_lo,y
adc cx16.r0L
sta cx16.VERA_ADDR_L
lda times320_mid,y
adc cx16.r0H
sta cx16.VERA_ADDR_M
lda #0
adc times320_hi,y
sta cx16.VERA_ADDR_H
lda p8v_eor_mode
bne +
lda p8v_color
sta cx16.VERA_DATA0
rts
+ lda p8v_color
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
}
sub circle(uword @zp xcenter, ubyte @zp ycenter, ubyte radius, ubyte color) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, color=color.
plot(cx16.r14, cx16.r15L, color)
}
}
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
; This version does bounds checks and clipping, but is a lot slower.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, color=color.
if cx16.r15 < HEIGHT
safe_plot(cx16.r14, cx16.r15L, color)
}
}
sub disc(uword @zp xcenter, ubyte @zp ycenter, ubyte @zp radius, ubyte color) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte last_y3 = ycenter+radius
ubyte last_y4 = ycenter-radius
ubyte new_y3, new_y4
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
; This version does bounds checks and clipping, but is a lot slower.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
uword last_y3 = ycenter+radius
uword last_y4 = ycenter-radius
uword new_y3, new_y4
while radius>=yy {
uword liney = ycenter+yy
if msb(liney)==0
safe_horizontal_line(xcenter-radius, lsb(ycenter+yy), radius*$0002+1, color)
liney = ycenter-yy
if msb(liney)==0
safe_horizontal_line(xcenter-radius, lsb(ycenter-yy), radius*$0002+1, color)
new_y3 = ycenter+radius
if new_y3 != last_y3 {
if msb(last_y3)==0
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
last_y3 = new_y3
}
new_y4 = ycenter-radius
if new_y4 != last_y4 {
if msb(last_y4)==0
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
last_y4 = new_y4
}
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
; draw the final two spans
yy--
if msb(last_y3)==0
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
if msb(last_y4)==0
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
}
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
; x in r0, y in r1, color.
%asm {{
clc
adc times320_lo,y
sta cx16.VERA_ADDR_L
txa
adc times320_mid,y
sta cx16.VERA_ADDR_M
lda #0
adc times320_hi,y
sta cx16.VERA_ADDR_H
lda p8v_eor_mode
bne +
lda cx16.r0L
sta cx16.VERA_DATA0
rts
+ lda cx16.r0L
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
sub safe_plot(uword xx, ubyte yy, ubyte color) {
; A plot that does bounds checks to see if the pixel is inside the screen.
if msb(xx)&$80!=0
return
if xx >= WIDTH or yy >= HEIGHT
return
plot(xx, yy, color)
}
asmsub pget(uword x @AX, ubyte y @Y) -> ubyte @A {
; returns the color of the pixel
%asm {{
jsr p8s_position
lda cx16.VERA_DATA0
rts
}}
}
sub fill(uword x, ubyte y, ubyte new_color) {
; reuse a few virtual registers in ZP for variables
&ubyte fillm = &cx16.r7L
&ubyte seedm = &cx16.r8L
&ubyte cmask = &cx16.r8H
&ubyte vub = &cx16.r13L
&ubyte nvub = &cx16.r13H
ubyte[4] amask = [$c0,$30,$0c,$03] ; array of cmask bytes
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 100
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
word[MAXDEPTH] @split @shared stack_xr
word[MAXDEPTH] @split @shared stack_y
byte[MAXDEPTH] @shared stack_dy
cx16.r12L = 0 ; stack pointer
word x1
word x2
byte dy
cx16.r10L = new_color
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
if cx16.r12L==MAXDEPTH
return
cx16.r0s = sy+sdy
if cx16.r0s>=0 and cx16.r0s<=HEIGHT-1 {
;; stack_xl[cx16.r12L] = sxl
;; stack_xr[cx16.r12L] = sxr
;; stack_y[cx16.r12L] = sy
;; stack_dy[cx16.r12L] = sdy
;; cx16.r12L++
%asm {{
ldy cx16.r12L
lda p8v_sxl
sta p8v_stack_xl_lsb,y
lda p8v_sxl+1
sta p8v_stack_xl_msb,y
lda p8v_sxr
sta p8v_stack_xr_lsb,y
lda p8v_sxr+1
sta p8v_stack_xr_msb,y
lda p8v_sy
sta p8v_stack_y_lsb,y
lda p8v_sy+1
sta p8v_stack_y_msb,y
ldy cx16.r12L
lda p8v_sdy
sta p8v_stack_dy,y
inc cx16.r12L
}}
}
}
sub pop_stack() {
;; cx16.r12L--
;; x1 = stack_xl[cx16.r12L]
;; x2 = stack_xr[cx16.r12L]
;; y = stack_y[cx16.r12L]
;; dy = stack_dy[cx16.r12L]
%asm {{
dec cx16.r12L
ldy cx16.r12L
lda p8v_stack_xl_lsb,y
sta p8v_x1
lda p8v_stack_xl_msb,y
sta p8v_x1+1
lda p8v_stack_xr_lsb,y
sta p8v_x2
lda p8v_stack_xr_msb,y
sta p8v_x2+1
lda p8v_stack_y_lsb,y
sta p8v_yy
lda p8v_stack_y_msb,y
sta p8v_yy+1
ldy cx16.r12L
lda p8v_stack_dy,y
sta p8v_dy
}}
yy+=dy
}
cx16.r11L = pget(xx as uword, lsb(yy)) ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>WIDTH-1 or yy<0 or yy>HEIGHT-1
return
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L!=0 {
pop_stack()
xx = x1
if fill_scanline_left_8bpp() goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
fill_scanline_right_8bpp()
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
xx++
while xx <= x2 {
if pget(xx as uword, lsb(yy)) == cx16.r11L
break
xx++
}
left = xx
} until xx>x2
}
sub set_vera_address(bool decr) {
; set both data0 and data1 addresses
position(xx as uword, lsb(yy))
cx16.r0 = cx16.VERA_ADDR
cx16.r1L = cx16.VERA_ADDR_H & 1 | if decr %00011000 else %00010000
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_CTRL = 1
cx16.VERA_ADDR = cx16.r0
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_CTRL = 0
}
sub fill_scanline_left_8bpp() -> bool {
set_vera_address(true)
cx16.r9s = xx
while xx >= 0 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx--
}
return xx==cx16.r9s
}
sub fill_scanline_right_8bpp() {
set_vera_address(false)
while xx <= WIDTH-1 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx++
}
}
}
sub text_charset(ubyte charset) {
; -- select the text charset to use with the text() routine
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase etc. etc.
cx16.screen_set_charset(charset, 0)
}
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) {
; -- 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
ubyte[8] @shared char_bitmap_bytes_left
ubyte[8] @shared char_bitmap_bytes_right
while @(textptr)!=0 {
chardataptr = charset_addr + (@(textptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
position(xx,lsb(yy))
yy++
%asm {{
ldx p8v_color
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8
- asl P8ZP_SCRATCH_B1
bcc +
stx cx16.VERA_DATA0 ; write a pixel
bra ++
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
+ dey
bne -
}}
}
xx+=8
yy-=8
textptr++
}
}
asmsub position(uword x @AX, ubyte y @Y) {
%asm {{
clc
adc times320_lo,y
sta cx16.VERA_ADDR_L
txa
adc times320_mid,y
sta cx16.VERA_ADDR_M
lda #%00010000 ; auto increment on
adc times320_hi,y
sta cx16.VERA_ADDR_H
rts
}}
}
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
}}
}
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r0+1
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
bne -
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
dex
bne -
+ ldx cx16.r0 ; remaining pixels
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
dex
bne -
+ rts
}}
}
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) clobbers(X) {
; this is only useful in 256 color mode where one pixel equals one byte value.
%asm {{
ldx #8
- asl cx16.r0
bcc +
sta cx16.VERA_DATA0
bra ++
+ sty cx16.VERA_DATA0
+ dex
bne -
rts
}}
}
%asm {{
; multiplication by 320 lookup table
times320 := 320*range(240)
times320_lo .byte <times320
times320_mid .byte >times320
times320_hi .byte `times320
}}
}
+17 -71
View File
@@ -1,11 +1,10 @@
%import syslib
%import textio
; Bitmap pixel graphics module for the CommanderX16
; Wraps the graphics functions that are in ROM.
; Only lo-res 320x240 256 color mode for now.
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
; For other resolutions or other color modes, use the "gfx_lores", "gfx_hires", or "monogfx" module instead.
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
@@ -16,7 +15,7 @@
graphics {
%option ignore_unused
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
extsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
const uword WIDTH = 320
const ubyte HEIGHT = 240
@@ -35,7 +34,7 @@ graphics {
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_mode(0, false)
txt.clear_screen()
cbm.CHROUT(147)
}
@@ -76,83 +75,30 @@ graphics {
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
;cx16.r0 = xcenter - radius/2
;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2
;cx16.r3 = radius*2
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
; Midpoint algorithm
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
cx16.GRAPH_draw_oval(xcenter - radius, ycenter - radius, radius*2, radius*2, false)
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
cx16.GRAPH_draw_oval(xcenter - radius, ycenter - radius, radius*2, radius*2, true)
}
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
sub oval(uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius) {
; specific for the X16 which has a kernal routine that can draw ovals
if h_radius==0 or v_radius==0
return
cx16.GRAPH_draw_oval(xcenter - h_radius, ycenter - v_radius, h_radius*2, v_radius*2, false)
}
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub filled_oval(uword xcenter, ubyte ycenter, uword h_radius, ubyte v_radius) {
; specific for the X16 which has a kernal routine that can draw ovals
if h_radius==0 or v_radius==0
return
cx16.GRAPH_draw_oval(xcenter - h_radius, ycenter - v_radius, h_radius*2, v_radius*2, true)
}
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
Loaded 100 of 320 files, more files were not shown because too many files have changed in this diff. Show more