Compare commits

..
259 Commits
Author SHA1 Message Date
Irmen de Jong 5a3ddf705a threshold 2 on ptr usages before no longer const
version 12.2-BETA
2026-05-10 03:08:54 +02:00
Irmen de Jong 20b29fb9b1 add getStaticAddressLowHigh to simplify address calculations and improve arithmetic code generation 2026-05-10 00:09:10 +02:00
Irmen de Jong fd3d59e159 fix sizeof() on a memory slab reference to return const 2 again
diskio.list_filenames() now checks filenames_buf_size minimum (>20)
2026-05-09 23:43:05 +02:00
Irmen de Jong 6d1dfd2a09 due to internal complexity, only pointers to bytes can be made const 2026-05-09 22:45:44 +02:00
Irmen de Jong 7095b14bcb checks 2026-05-09 22:05:57 +02:00
Irmen de Jong 1fe5ff18b1 memory() turned into 2 new ast nodes (slab reservation and slab ref) - still somewhat broken 2026-05-09 11:16:09 +02:00
Irmen de Jong 793b2a703b first step refactoring memory() calls 2026-05-08 19:32:33 +02:00
Irmen de Jong 53948be68f better skill name 2026-05-08 19:30:00 +02:00
Irmen de Jong 3d0dbb71ce created coding agent skills for prog8 and 6502 asm 2026-05-08 19:17:39 +02:00
Irmen de Jong 3dfa804e52 more optimized image conversion script/library 2026-05-07 21:59:53 +02:00
Irmen de Jong fd93d103f6 fix const value problems related to memory(), and fix a bug in constvalue calculation for ArrayIndexedExpression 2026-05-07 03:07:01 +02:00
Irmen de Jong 28977996e2 non-const pointers now can get optimized to const too. 2026-05-06 19:50:21 +02:00
Irmen de Jong e867cb88a7 fix astprinter crash,
IR: fixed using memory() as initialization value for a const uword or const pointer crashed the compiler
2026-05-06 01:31:40 +02:00
Irmen de Jong 1ba8dfcc8b actually doing some const-folding for most const pointer usages. 2026-05-06 00:31:47 +02:00
Irmen de Jong cd2d30c2c7 const pointer support added (but lacks proper const-folding still) 2026-05-05 23:21:32 +02:00
Irmen de Jong a47d2fa40d hint about efficient buffer iteration and skeleton files 2026-05-05 18:39:40 +02:00
Irmen de Jong 6be70af79f fix Inliner crash when using void in multi-value assignment.
update to gradle 9.4.1, tweak agent instructions.
2026-05-03 21:01:57 +02:00
Irmen de Jong 15a28814d5 optimized peekl() mklong() mklong2() to write directly into target variable if possible 2026-05-03 13:42:13 +02:00
Irmen de Jong 049acf0e98 implement long min() max() clamp() and abs() 2026-05-03 08:31:46 +02:00
Irmen de Jong df0d9e567f fix codegen for min() and max() for signed bytes 2026-05-03 07:11:42 +02:00
Irmen de Jong 78920aa115 script to create disk images 2026-05-03 05:15:19 +02:00
Irmen de Jong 1bb9ad5f46 fix peekw target register assignment crash 2026-05-02 23:38:56 +02:00
Andrew Gillham 1f30a06429 Don't require the BASIC launcher when the target has a custom launcher defined. (#224) 2026-05-02 23:38:47 +02:00
Irmen de Jong 5560c342db Merge branch 'optimizing_ifs' 2026-05-02 18:47:04 +02:00
Irmen de Jong e073c007ad better error when trying to run non-6502 code in the 6502 simulator 2026-05-02 18:37:07 +02:00
Irmen de Jong 9d1fa856d4 added unit tests that use the ksim65 simulator to actually run the compiled code 2026-05-02 06:15:09 +02:00
Irmen de Jong 7a26bb972f fix problem where no efficient code was generated anymore for pointer+index 2026-05-02 03:05:19 +02:00
Irmen de Jong e72a4723f9 Merge branch 'master' into optimizing_ifs 2026-05-02 02:17:23 +02:00
Irmen de Jong 96f0d112d2 fix problem where no efficient code was generated anymore for pointer+index 2026-05-02 02:14:18 +02:00
Irmen de Jong eac1b64f81 fix invalid optimization from commit b175dd0265 2026-05-02 00:39:05 +02:00
Irmen de Jong 0011dd8f4c Merge branch 'master' into optimizing_ifs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt
#	compiler/test/TestCompilerOnExamples.kt
2026-05-01 19:54:16 +02:00
Irmen de Jong 5ce463d133 added jitpack.io library repository and ksim65 library for possible future use 2026-05-01 18:59:58 +02:00
Irmen de Jong 5926473281 working on improving operand order swap 2026-04-30 05:44:13 +02:00
Irmen de Jong 0a1d06b7f2 corrected associative -> commutative operators names, simplified maySwapOperandOrder logic 2026-04-30 01:13:50 +02:00
Irmen de Jong f98e7d6ad9 optimizing a=b+c; use 64tass local anonymous labels 2026-04-29 22:20:14 +02:00
Irmen de Jong 37896aeed4 optimizing a=b+c 2026-04-29 19:36:38 +02:00
Irmen de Jong b175dd0265 optimizing if-expressions too: more tweaks 2026-04-28 19:54:29 +02:00
Irmen de Jong 310d17d8bb optimizing if-expressions too: typecast const operand unwrapping 2026-04-28 19:54:29 +02:00
Irmen de Jong 371bd4b120 optimizing if-expressions too: final step 2026-04-28 19:54:29 +02:00
Irmen de Jong 3965d77813 optimizing if-expressions too: optimized word and long comparisons more 2026-04-28 19:54:29 +02:00
Irmen de Jong e0ed761661 optimizing if-expressions too: optimized word and long comparisons 2026-04-28 19:54:29 +02:00
Irmen de Jong 51d5261776 optimizing if-expressions too: pointer deref support 2026-04-28 19:54:29 +02:00
Irmen de Jong b85ac38b3b optimizing if-expressions too 2026-04-28 19:54:29 +02:00
Irmen de Jong 6ff9153d9b attempt to optimize word and long comparisons 2026-04-28 19:54:29 +02:00
Irmen de Jong e0d2e407e4 sort operands on complexity 2026-04-28 19:54:29 +02:00
Irmen de Jong 0117ab410b fix an uninitialized parent issue 2026-04-28 19:52:28 +02:00
Irmen de Jong b78a0cf076 make now auto selects system gradle or gradlew wrapper script 2026-04-28 13:29:51 +02:00
Irmen de Jong 722d52c939 revert status channel read on the C64 because it messes with the open file I/O
culprit was commit 2a9ac45cad
2026-04-27 23:37:17 +02:00
Irmen de Jong 162e5ffe3c agent instruction 2026-04-27 11:32:02 +02:00
Irmen de Jong 7383ffae46 fix UnusedCodeRemover wrongly removing var initializers 2026-04-27 00:19:16 +02:00
Irmen de Jong 1b4635d27e missing file 2026-04-26 00:22:41 +02:00
Irmen de Jong 1c376aa9d1 fix some 6502 code gen regressions 2026-04-26 00:18:28 +02:00
Irmen de Jong 91ff17f666 fix some 6502 code gen regressions 2026-04-25 23:32:30 +02:00
Irmen de Jong 5a81021ddc -out now creates the directory if it does not exist yet 2026-04-25 22:48:24 +02:00
Irmen de Jong 9fdd9254f1 fix missing CMP in IR after conditional expression 2026-04-25 22:23:10 +02:00
Irmen de Jong ae4c4ca160 IR: optimized boolean expression codegen (use status flags directly more often instead of transforming to 1 / 0 values) 2026-04-24 22:26:16 +02:00
Irmen de Jong 7ab4880c4a IR: optimized poke() and pokemon() 2026-04-24 19:38:18 +02:00
Irmen de Jong 5c16a40f33 IR: optimized float and bol comparison code gen 2026-04-24 09:41:45 +02:00
Irmen de Jong 6f9f31cd8a better const folding for multivalue assignments / vardecl inits in StatementReorderer 2026-04-24 07:18:01 +02:00
Irmen de Jong 7cd70acc01 implemented const folding of lmh() and divmod() 2026-04-24 06:02:16 +02:00
Irmen de Jong 2d3c20d347 c128: define golden ram area at $1300-$1BDF 2026-04-24 05:26:25 +02:00
Irmen de Jong b05bc9ac0e add IR peephole optimization for x * negative power of 2 2026-04-24 04:57:28 +02:00
Irmen de Jong 2589f953c3 fix IR peephole optimizer fault that incorrectly optimized 20-x expression 2026-04-24 04:49:09 +02:00
Irmen de Jong a40f9de9e6 fix IR codegen issue with DIVMOD and some other instructions 2026-04-24 04:22:29 +02:00
Irmen de Jong e7491a56ef todo 2026-04-23 02:50:56 +02:00
Irmen de Jong 2bf5d1406d make cx16/starszoom example fill the whole screen, and rotate 2026-04-22 19:52:11 +02:00
Irmen de Jong c32e7ee57b improve docs about operator precedence 2026-04-22 02:46:17 +02:00
Irmen de Jong a633b5a4b5 split Agent Prog8 language instructions 2026-04-21 22:13:03 +02:00
Irmen de Jong a5c078be14 fix inline warning 2026-04-21 06:27:40 +02:00
Irmen de Jong 547d21a42b added more redundant variable initialization removals 2026-04-21 06:13:39 +02:00
Irmen de Jong e2e0992d02 verafx: fix copy() and clear() unrolling 2026-04-21 03:55:28 +02:00
Irmen de Jong 10fd321537 use STZ in certain repeat loops on 65c02
remove unused memcopy16_up
2026-04-21 00:07:29 +02:00
Irmen de Jong fb26a7af3b redundant 2026-04-19 11:06:46 +02:00
Irmen de Jong 8592a3a200 use VarDecl builder instead of constructor 2026-04-19 02:26:50 +02:00
Irmen de Jong 930697dad5 using builder to improve massive lists of constructor params 2026-04-19 01:39:50 +02:00
Irmen de Jong a60ba27bad improve LSP 2026-04-18 23:30:12 +02:00
Irmen de Jong 87480b150a fix monogfx doublebuffer 2026-04-18 20:20:33 +02:00
Irmen de Jong 0cb9d408ff made some internal routines and variables in the library routines private 2026-04-18 19:55:10 +02:00
Irmen de Jong c784fd03fd remove internal_string_copy because strings can no longer be assigned by value that always needs strings.copy() 2026-04-18 19:29:05 +02:00
Irmen de Jong 5dcc11d67e try to avoid crash when importing modules twice (via symlink for instance). 2026-04-18 18:06:41 +02:00
Irmen de Jong e36a589e23 try to avoid crash when importing modules twice (via symlink for instance). 2026-04-18 12:56:30 +02:00
Irmen de Jong fe6a8f63a0 cx16: make gfx_lores actually use the kernal's screen mode 128 (keeps text layer)
also for gfx_hires and monogfx. fixes #220
2026-04-18 12:25:46 +02:00
Irmen de Jong 60e2a38060 fix IR read error on structs with typed pointer fields 2026-04-17 21:01:51 +02:00
Irmen de Jong d808818506 refactor grammar for signatures 2026-04-15 01:51:58 +02:00
Irmen de Jong 0f72bafeae improve code for in-place assignments to indexed pointers 2026-04-14 19:11:13 +02:00
Irmen de Jong c794d2a50b fix invalid IR opcode used for inplace assignments to memory targets 2026-04-14 15:32:12 +02:00
Irmen de Jong db84c375f1 implement a missing long pointer assignment case 2026-04-14 01:55:02 +02:00
Irmen de Jong 03fa6422b3 fix codegen for split array access using some peek/poke patterns 2026-04-13 21:43:39 +02:00
Irmen de Jong ad34633ca9 fix codegen for split array access using pointer + or - offset 2026-04-13 21:18:32 +02:00
Irmen de Jong d5eef79892 implement @nosplit pointer arrays 2026-04-13 20:41:57 +02:00
Irmen de Jong d5a53c5d21 implement missing division operations in VM 2026-04-13 18:48:35 +02:00
Irmen de Jong bd51296a33 implement V flag on comparisons in the virtualmachine 2026-04-12 20:47:56 +02:00
Irmen de Jong c0103f149e more general implementation of struct definition equality check see github #198 2026-04-12 20:43:19 +02:00
Irmen de Jong 86174e49c9 fix invalid ast node lookup in replacements 2026-04-12 16:41:00 +02:00
Irmen de Jong 0be336c8a8 lineclip test code 2026-04-12 16:36:10 +02:00
Irmen de Jong d836f48f38 updated prog8 instructions 2026-04-12 13:00:19 +02:00
Irmen de Jong 952d7b3162 fix faulty float optimization and ptrderef equality check 2026-04-11 23:55:59 +02:00
Irmen de Jong 5638eb1c53 asm optimizations: fix inconsistent line property, float copy optimization bug, Y register modification bug 2026-04-11 19:24:12 +02:00
Irmen de Jong d3f19bb438 fix lacking checks for IO registers in asm optimizer, caused incorrect instruction removals 2026-04-11 18:01:57 +02:00
Irmen de Jong be60021edc fix lacking checks for IO registers in asm optimizer, caused incorrect instruction removals 2026-04-11 04:18:22 +02:00
Irmen de Jong b2ed3b5934 add C128 MMU control registers to 'IO' registers ranges.
remove unused GoldenRam configuration stuff.
2026-04-10 16:52:28 +02:00
Irmen de Jong 59d0f7d49c 2-dimensional array support 2026-04-10 04:22:38 +02:00
Irmen de Jong 47700918cf preparing for improvements in pointer dereference grammar 2026-04-09 19:29:32 +02:00
Irmen de Jong db7edde2aa report multiple parse errors at once.
grammar rules improvements.
2026-04-09 07:06:03 +02:00
Irmen de Jong e2702d3e22 IRFileWriter and IRFileReader improvements 2026-04-09 05:06:39 +02:00
Irmen de Jong 74475d023b improving IR symbolic references 2026-04-09 03:54:51 +02:00
Irmen de Jong 9ae0623ad0 making MemoryAddress an Unsigned Int 2026-04-09 02:40:24 +02:00
Irmen de Jong 33b34b990e making RegisterNum type safe 2026-04-08 02:57:45 +02:00
Irmen de Jong 6dc96bb8f0 making IRInstruction parameters type safe 2026-04-07 20:15:55 +02:00
Irmen de Jong aeca6350da make IR instruction number order more pleasant to read 2026-04-07 14:39:28 +02:00
Irmen de Jong 6a340aa59d refactor void return value handling for multivalue results 2026-04-07 13:41:07 +02:00
Irmen de Jong 207650fbc2 fix multivalue pha/pla regression 2026-04-07 05:52:50 +02:00
Irmen de Jong 4fb3df977e fix correct evaluation of floats in multi-value returns 2026-04-07 05:41:27 +02:00
Irmen de Jong 9f742e1806 6502 codegen: handle floats as part of multi-value result 2026-04-07 04:50:23 +02:00
Irmen de Jong aca6c22098 IR codegen: handle floats as part of multi-value result 2026-04-07 04:31:17 +02:00
Irmen de Jong 4b45dae68a IR codegen: optimized array +/-/* in place 2026-04-07 04:12:44 +02:00
Irmen de Jong 0b68481fda optimized 6502 codegen for split array |=, &=, ^= 2026-04-06 23:27:25 +02:00
Irmen de Jong bba08f55de use compact loop to copy longs instead of 4x lda/sta pairs 2026-04-06 20:47:20 +02:00
Irmen de Jong 9e5df3d2d4 Optimize the IfExpression code generation to be more like regular if-else code (tighter branch code) 2026-04-06 20:25:41 +02:00
Irmen de Jong 5c1aa2307d for loops where the counter variable is unused in the loop body and count is constant -> simple repeat loops 2026-04-05 23:35:58 +02:00
Irmen de Jong 3129f5e071 fix IR return type assignment error 2026-04-05 12:44:06 +02:00
Irmen de Jong 04a8181c05 fix error when assigning str to ^^ubyte in multi-value assign 2026-04-05 12:26:20 +02:00
Irmen de Jong 27bf9c9a4c fix compiler crash when comparing str with number 2026-04-04 02:47:02 +02:00
Irmen de Jong 7658a0ab12 added strings.isxdigit()
fixed size bugs in several virtual string to number conv routines
2026-04-03 23:33:00 +02:00
Irmen de Jong 42d3340fe4 fix an msb() codegen crash when assigning back to word 2026-04-03 22:15:08 +02:00
Irmen de Jong 1100ab0d22 performance tweaks 2026-04-03 19:54:37 +02:00
Irmen de Jong 96eb3f0fb2 ImportFileSystem thread safe, IR performance tweaks 2026-04-03 06:53:08 +02:00
Irmen de Jong 1ed618c474 performance improvements in parser 2026-04-03 04:31:29 +02:00
Irmen de Jong e6c26587d6 improve Position accuracy 2026-04-03 03:42:17 +02:00
Irmen de Jong 4739bd087a IR: avoid redundant CMPI in if-expressions, remove dead stores 2026-04-02 23:41:03 +02:00
Irmen de Jong 92751d9285 document links in the LSP, remove repl/beanshell 2026-04-02 21:55:56 +02:00
Irmen de Jong 0792571b28 parse errors in the LSP 2026-04-02 02:29:38 +02:00
Irmen de Jong 511564dd1e symbol extraction in the LSP 2026-04-02 01:14:36 +02:00
Irmen de Jong 3da3d63eb4 more inlining support
fix invalid character in LSP
2026-04-01 19:04:03 +02:00
Irmen de Jong 3c859512a5 TDD inilining with parameters tests 2026-04-01 00:05:22 +02:00
Irmen de Jong 45c05522d5 fix inliner bug 2026-03-31 21:25:34 +02:00
Irmen de Jong db417ab9ce refactoring AstModification 2026-03-31 19:59:56 +02:00
Irmen de Jong c17a3aff5c refactoring AstModification 2026-03-31 15:37:55 +02:00
Irmen de Jong 6184a5004c wrapping child nodes mutable list away to avoid direct access that might corrupt it 2026-03-31 10:33:54 +02:00
Irmen de Jong 07772bddfd better zeropage tests and symboltable tweaks 2026-03-31 07:21:56 +02:00
Irmen de Jong 15bf4c7692 unit test helpers refactoring 2026-03-31 05:50:30 +02:00
Irmen de Jong f38c701658 some support for inlining multi-value returns (where there are no function parameters to deal with) 2026-03-31 04:42:34 +02:00
Irmen de Jong 10ce992b35 IR example metrics now count instructions not p8ir lines 2026-03-29 22:46:51 +02:00
Irmen de Jong 331e2b8f2b improve ast source position tracking 2026-03-29 19:49:42 +02:00
Irmen de Jong 05093e35c8 refactor inliner 2026-03-29 11:00:26 +02:00
Irmen de Jong 5fd11ca036 more consistent allowance of inline keyword 2026-03-29 10:15:51 +02:00
Irmen de Jong 5330069b53 disallow weird sizeof argument types 2026-03-29 09:50:13 +02:00
Irmen de Jong 5408a3ca68 fix compiler crash on certain pointer type struct fields
added new compiler options for debugging purposes
2026-03-29 08:25:53 +02:00
Irmen de Jong cecf49b8e3 IR: attempt to optimize negative comparisons by using direct status flag branches instead 2026-03-29 01:23:24 +01:00
Irmen de Jong 65c17b920a add txt.iso() to virtual target for source code compatibility and tests 2026-03-29 00:39:04 +01:00
Irmen de Jong 7666b72fea implement missing codegen for returning multivalue result of a subroutine call 2026-03-28 23:38:52 +01:00
Irmen de Jong bcd12a0fc7 improve handling of boolean literals in logical expressions with -noopt 2026-03-28 19:19:14 +01:00
Irmen de Jong dd0828a1fc fix a circular dependency in gfx_lores.p8 between line and plot 2026-03-28 15:35:56 +01:00
Irmen de Jong 770532a039 tweak line drawing routine for better accurracy
better agent instructions about asmsubs
2026-03-28 14:22:21 +01:00
Irmen de Jong 400149d5a3 standardize gradle project configurations across submodules 2026-03-28 10:08:47 +01:00
Irmen de Jong d6ed40af36 SymbolTable improvements 2026-03-28 04:14:26 +01:00
Irmen de Jong 570ea50a3b cleanups 2 2026-03-28 01:22:33 +01:00
Irmen de Jong 04b9f8e749 cleanups 2026-03-28 00:48:29 +01:00
Irmen de Jong 2bd7f3e210 improved SimpleAST optimizer 2026-03-27 01:13:38 +01:00
Irmen de Jong 00bae6e746 comment 2026-03-27 00:20:35 +01:00
Irmen de Jong f6d5e88f31 Fix test cases 2026-03-26 23:32:27 +01:00
Irmen de Jong 8d44a1b226 implemented bitmap text drawing for the virtual target (so monogfx.text() now works there) 2026-03-26 22:47:44 +01:00
Irmen de Jong a47be61812 fix bobs example, add fps counter to cobra example 2026-03-25 19:35:36 +01:00
Irmen de Jong 68226a8fcd Update Kotlin and dependencies to latest versions
- Bump Kotlin to 2.3.20 across all configurations.
- Upgrade `kotlin-result-jvm` to 2.3.1 in all modules.
- Update Gradle wrapper to 9.4.0.
- Add `poweroff_system()` function in virtual target symboldump
2026-03-25 17:51:01 +01:00
Irmen de Jong 2016883f6c compare p8ir line counts instead of file size 2026-03-21 02:44:21 +01:00
Irmen de Jong d4dd2f02e8 new example floatparse routine 2026-03-21 02:06:46 +01:00
Irmen de Jong bc959435ec fix missing const-evaluation for abs() and clamp()
add `poweroff_system()` for virtual target function
clarify CX16 usage and debugging tips
2026-03-20 02:31:17 +01:00
Irmen de Jong e5c6e4b854 add output size verification for compiled examples; include PET target in tests 2026-03-19 20:16:47 +01:00
Irmen de Jong 4294658d71 fix IR symbol lookup for deeply scoped variables 2026-03-18 23:31:39 +01:00
Irmen de Jong 0a7352de99 add disableCache option to SymbolTable to assist debugging and optimize behavior when -noopt is used 2026-03-18 23:12:48 +01:00
Irmen de Jong bb907aa2af fix chained aliasing errors 2026-03-18 21:52:42 +01:00
Irmen de Jong e5d74d55b5 vm memory optimizations 2026-03-18 18:02:13 +01:00
Irmen de Jong c291d7d4a8 const memory() support in struct initializers 2026-03-18 04:16:05 +01:00
Irmen de Jong d2633187a6 more work on making memory() const (6502) 2026-03-18 02:57:59 +01:00
Irmen de Jong ca56cf26c5 more work on making memory() const (virtual target only for now) 2026-03-18 02:20:50 +01:00
Irmen de Jong f72bec3c4a working on making memory() accepted as constant initializer value 2026-03-17 23:14:03 +01:00
Irmen de Jong 06b4ec600a more permanent solution for the struct name prefix bug when merging blocks, fixes #198 2026-03-17 23:09:52 +01:00
Irmen de Jong 410e330889 add --nostdlib compiler option to disable loading built-in standard libraries 2026-03-17 21:46:33 +01:00
Irmen de Jong 11f187bda8 implement signed % remainder operator for virtual target 2026-03-17 03:36:38 +01:00
Irmen de Jong a53e7c7050 fix divmod result when dividing by 0 2026-03-16 03:25:34 +01:00
Irmen de Jong e56ccdbc3a add signed divmod() support for virtual target 2026-03-16 01:19:02 +01:00
Irmen de Jong 51264267ad fix multi variable initialization from builtin function call that returns multiple values (such as divmod) 2026-03-16 00:45:37 +01:00
Irmen de Jong 8be0cd3c19 fix certain value copying through pointer dereferences 2026-03-16 00:10:47 +01:00
Irmen de Jong decfbfc8cc allow float constants even without float option enable 2026-03-15 22:46:35 +01:00
Irmen de Jong 652da4c692 fix some (not all) problems with directly returning multivalue returncalls 2026-03-15 22:33:17 +01:00
Irmen de Jong ce58f0e759 fix virtual target diskio.f_write(), added f_tell() and f_seek()
cx16 diskio.f_seek() and f_seek_w() now return success status
2026-03-15 21:04:06 +01:00
Irmen de Jong 8ba8efbc3e added sys.die() , a bare bones emergency exit function 2026-03-14 22:13:13 +01:00
Irmen de Jong 276121bfb3 updated Kate syntax file to fix string match problem 2026-03-14 19:06:17 +01:00
Irmen de Jong 5c9c6bd023 updated syntax files 2026-03-14 15:37:27 +01:00
Irmen de Jong 86f331aa39 thread locking tweaks 2026-03-14 00:13:56 +01:00
Irmen de Jong e61328b6d6 update Java dependency to 17, update results library version 2026-03-14 00:04:11 +01:00
Irmen de Jong 9502a6f590 fix invalid augmented assignment optimization, and a call to pop() that crashed a symbol prefix test because the call wasn't defined as a builtin 2026-03-13 22:08:36 +01:00
Irmen de Jong 01748517ae most often used long-copies now done with shorter loop instead of unrolled lda/sta 2026-03-13 21:21:38 +01:00
Irmen de Jong 3890191981 implement missing mul_long routine for long-long multiplication support 2026-03-13 20:32:42 +01:00
Irmen de Jong c20e5b788b implement missing square_long routine
improve agents system prompt file
2026-03-13 18:21:47 +01:00
Irmen de Jong fb3a001a99 attempting to optimize redundant variable initializations (STZs) 2026-03-12 21:38:28 +01:00
Irmen de Jong 04910ffbc0 add enum syntax (sugar for list of const decls) 2026-03-10 20:33:43 +01:00
Irmen de Jong caa991e478 IR: remove redundant add when pointer dereferencing 2026-03-08 21:31:31 +01:00
Irmen de Jong f9767a5b1b cx16: added sys.waitirq() that just does a WAI like waitvsync() but it is explicit about its behavior 2026-03-08 18:05:43 +01:00
Irmen de Jong 3b68741208 replace indexed pointer deref expressions with peek(ptr+index) 2026-03-08 12:59:34 +01:00
Irmen de Jong b8ef64139a avoid always including profile support routine in resulting asm 2026-03-08 03:16:17 +01:00
Irmen de Jong 1ad9e0fc46 implement missing struct field array indexing and faulty pointer dereferencing 2026-03-08 02:43:52 +01:00
Irmen de Jong ce244906ef added wavfile and adpcm library modules (adpcm only on X16) 2026-03-07 20:47:23 +01:00
Irmen de Jong 63ab79157f optimize palette module routines slightly to no longer use vaddr()
fix unused symbol warnings in bcd library module
2026-03-05 03:55:08 +01:00
Irmen de Jong 9ac0cfd14c fix palette.fade_step_colors() for color indexes >=128 2026-03-05 03:05:19 +01:00
Irmen de Jong 02c544bb62 fix cx16.getlfs() and add diskio.reuse_last_drive()
update cx16images.py to use improved quantization
2026-03-05 01:11:02 +01:00
Irmen de Jong 998d1ad446 IR: storezi now also takes an immediate offset argument, optimized some code gen to utilize that offset better insteads of doing separate ADDs 2026-03-04 01:12:01 +01:00
Irmen de Jong 89e38c6d42 IR: loadfield/storefield are now just loadi/storei 2026-03-03 21:17:27 +01:00
Irmen de Jong a326c6f079 adding multivalue vardecls and assignments 2026-03-03 00:06:48 +01:00
Irmen de Jong acf70d68ca todo 2026-03-02 02:54:16 +01:00
Irmen de Jong 17850d3bc1 fix certain long initialization type error 2026-03-02 02:14:31 +01:00
Irmen de Jong d03e49a92f implemented 6502 long loops with step >1 2026-03-01 03:13:54 +01:00
Irmen de Jong 62c5b531c5 implemented 6502 long loops with step 1 or -1 2026-03-01 02:06:18 +01:00
Irmen de Jong 8911f58bd2 fix IR long comparisons (used in for loops for instance) 2026-03-01 01:45:07 +01:00
Irmen de Jong da6e47d422 fix IR long comparisons (used in for loops for instance) 2026-03-01 01:17:22 +01:00
Irmen de Jong 10d80d0274 tinyc64 custom config: move SCRATCH_PTR to $04+$05, from $9b+$9c because $9b is used by JiffyDOS. see #217 2026-02-28 23:19:57 +01:00
Irmen de Jong 23b9a2792d C64: move SCRATCH_PTR to $04+$05, from $9b+$9c because $9b is used by JiffyDOS. Fixes #217 2026-02-28 22:55:44 +01:00
Irmen de Jong 0a52cd2cee changed math.crc16() to now also take an initvalue and xorout value, to make it more flexible in calculating various variants of CRC16 checksums 2026-02-28 18:13:00 +01:00
Irmen de Jong fedf342cee fix interned string problem with -profiling 2026-02-28 16:46:27 +01:00
Irmen de Jong 2bafc161d6 attempt to fix doc 2026-02-27 23:03:14 +01:00
Irmen de Jong d0c5e35f0a attempt to fix doc 2026-02-27 22:56:58 +01:00
Irmen de Jong 859a02fb0a adding profiling instrumentation support 2026-02-27 22:41:42 +01:00
Irmen de Jong 2a53dbc1f3 tweaks 2026-02-27 01:56:55 +01:00
Irmen de Jong 63cba4825a doc tweaks 2026-02-26 01:44:36 +01:00
Irmen de Jong 08e3652924 sgn() can look directly at msb if arg is a variable 2026-02-24 22:11:50 +01:00
Irmen de Jong 10796c09c7 cleanup weiord multi-return type handling more 2026-02-24 21:42:44 +01:00
Irmen de Jong a3cbf4c3db fix divmod doc 2026-02-23 22:01:59 +01:00
Irmen de Jong bdbdf6e5cd add lmh(longvalue) builtin function that returns the low, mid, hi (=bank) bytes of a long
symboldumps now also contain the builtin functions
2026-02-23 21:30:39 +01:00
Irmen de Jong 1b5dd980df optimize result register cases for mklong() and mkword() 2026-02-23 02:07:43 +01:00
Irmen de Jong f6b59c6832 optimize some result register cases for peekw() 2026-02-23 00:54:12 +01:00
Irmen de Jong fe8d904792 fixing result register assignment from builtin functions 2026-02-22 19:54:21 +01:00
Irmen de Jong 9736ac19d1 implementing multiple return values for builtin functions 2026-02-22 19:54:21 +01:00
Irmen de Jong 14abc1f0b7 clarify compiler errors output 2026-02-22 03:22:20 +01:00
Irmen de Jong effc7f1b56 optimize and fix diskio.status_code() also for C64 and PET 2026-02-21 11:07:57 +01:00
Irmen de Jong 093be9f2dd sys.push(), sys.pop() etc etc are now builtin functions again push() pop() to avoid storing value in temporary variables
this means that all of the syslib.p8 library files no longer contain all those stack related asmsubs
2026-02-21 02:11:34 +01:00
Irmen de Jong 9aff85f08a optimize diskio.status_code() size 2026-02-20 20:52:49 +01:00
Irmen de Jong f2077cb56f sqrt_long() no longer allocates a bunch of zeropage variables 2026-02-20 01:23:33 +01:00
Irmen de Jong 2a9ac45cad diskio: make f_open, f_open_w, and exists to read the command channel for success status rather than depending on flaky ST 2026-02-18 02:05:47 +01:00
Irmen de Jong e15452b110 doc 2026-02-17 21:49:35 +01:00
Irmen de Jong a64b586a43 Merge branch 'swap-statement' 2026-02-17 21:00:43 +01:00
Irmen de Jong 2613d45cd5 swap() is now a statement instead of a builtin function call 2026-02-17 20:56:21 +01:00
Irmen de Jong c25fbbccfb update kotlin to 2.3.10 2026-02-17 16:33:21 +01:00
Irmen de Jong 77f0cf8e89 defer stack doc and better error message for invalid range syntax 2026-02-16 23:00:50 +01:00
Irmen de Jong 74ca190ce0 long args call convention is to pass via parameter variable not R14R15 2026-02-15 17:36:09 +01:00
Irmen de Jong 6a0b22bb64 implement sqrt(long) 2026-02-15 17:11:31 +01:00
Irmen de Jong 07be7f0154 fix long argument @R0R1 register usage in regular subroutines 2026-02-15 14:54:47 +01:00
Irmen de Jong d1383813d2 version 12.1.1 2026-02-13 22:11:29 +01:00
Irmen de Jong 3bd2064c3a mention empty file issue 2026-02-12 22:10:53 +01:00
Irmen de Jong f95f568319 fix mflpt5 overflow error message 2026-02-12 02:29:10 +01:00
Irmen de Jong 5eb873b722 conv.str2long() added. alias problems detected. 2026-02-12 00:38:42 +01:00
Irmen de Jong 3f181b0ede allow alias to blocks 2026-02-10 22:51:05 +01:00
Irmen de Jong d6c8fc0c61 alias refactorings 2026-02-10 21:08:35 +01:00
Irmen de Jong 709ced1bca alias test program, replace alias chains 2026-02-10 21:08:35 +01:00
Irmen de Jong 3b16d08246 arrays can contain pointers too 2026-02-10 21:03:07 +01:00
Irmen de Jong b9a6deea25 implement ror / rol on ptr expressions 2026-02-08 21:20:13 +01:00
Andy De George 099514a2c0 Fix error output file path: Encode to URI (#214) 2026-02-06 22:09:58 +01:00
Irmen de Jong ed02b84f7d micro optimization for containment checks in compiler 2026-02-06 02:43:12 +01:00
Irmen de Jong 7d6e67e251 fix numeric type error when comparing undefined symbols 2026-02-04 23:55:08 +01:00
Irmen de Jong 4aa364925d todo 2026-02-02 23:02:21 +01:00
374 changed files with 38971 additions and 17885 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
description: Startup workflow to load project context
---
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
+166
View File
@@ -0,0 +1,166 @@
---
name: asm6502-coder
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
license: MIT
compatibility: opencode
---
# 65(C)02 Assembly Coder Skill
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
## Debugging Generated Assembly
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
## Assembler: 64tass Syntax
- **NOT ca65/cc65** or other assemblers. Key differences:
- `.proc` / `.pend` for procedures (scoping)
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
- Example:
```asm
- dex ; backward label '-'
bne - ; branch to most recent '-' (the dex above)
ldx #5
+ dex ; forward label '+'
bne + ; branch to this same '+' (forward)
sta $400
+ lda #0 ; second '+'
bne ++ ; branch to the '++' below
rts
++ inc $d020 ; third forward label
```
- Data directives: `.byte`, `.word`, `.dword`
- Equates: `label = value` (not `label .equ value` or `#define`)
- Zero-page variables defined with `=`
- `.text` for inline string data
## Instructions
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
## 6502 vs 65C02
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
- Check the target before using 65C02-specific instructions
## Calling Convention / Register Conventions
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
- **X register**: 8-bit, often used for indexing, loop counters
- **Y register**: 8-bit, often used for indirect addressing index
- **Processor Status (P)**: flags — carry (C), zero (Z), negative/N (sign bit 7), overflow (V), decimal (D), interrupt (I), break (B)
- **No caller-saved vs callee-saved convention** — list all modified registers in `clobbers (A, X, Y)` when writing `asmsub`
- The CPU stack (SP, $0100-$01FF) is limited (usually ~128 bytes free). Do not overflow it
## Assembly within Prog8 Programs
### `%asm {{ }}` blocks
- Embed arbitrary 64tass assembly directly in your Prog8 source
- Access Prog8 symbols using their prefixed names (see below)
- Can be placed inside subroutines or at block level
### `asmsub` (assembly subroutine)
- For kernel (ROM) routines or low-level assembly
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
Example:
```prog8
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
%asm {{
x1 = cx16.r0
x2 = cx16.r1
lda x1 ; use alias, not "_x1"
}}
}
```
### `asmsub` parameter annotation reference
| Annotation | Register | Size |
|------------|----------|------|
| `@A` | Accumulator | 8-bit |
| `@X` | X register | 8-bit |
| `@Y` | Y register | 8-bit |
| `@AX` | A (low) + X (high) | 16-bit |
| `@AY` | A (low) + Y (high) | 16-bit |
| `@R0`-`@R15` | cx16 virtual registers | 16-bit each |
| `@FAC1`/`@FAC2` | Floating-point accumulators | 5-byte float |
| `@Pc` | Carry flag | bool |
| `@Pz` | Zero flag | bool |
## Accessing Prog8 Symbols from Assembly
All Prog8 symbols are prefixed when accessed from assembly:
| Prefix | Refers to | Example |
|--------|-----------|---------|
| `p8v_` | Variables, parameters | `p8v_myvar` |
| `p8s_` | Subroutines | `p8s_mysub` |
| `p8b_` | Blocks | `p8b_myblock` |
| `p8c_` | Constants, enum members | `p8c_myconst`, `p8c_MyEnum_Member` |
| `p8l_` | Labels | `p8l_mylabel` |
| `p8t_` | Struct types | `p8t_MyStruct` |
| `p8_` | Other symbols | |
- **Fully qualified**: e.g., `p8b_myblock.p8v_myvar`, `p8b_myblock.p8s_mysub.p8v_localvar`
- Within a `.proc` (subroutine) in your assembly, short names often work (assembler scoping)
- **`%option no_symbol_prefixing`**: disables all prefixes. Stdlib modules (`cbm`, `cx16`, `txt`) use this — you can write `cbm.CHROUT` directly
- **Split word arrays**: two separate byte arrays — append `_lsb` and `_msb` to the name: `p8v_myarray_lsb`, `p8v_myarray_msb`
## Zeropage Usage
- Do NOT use arbitrary zeropage locations. Only use these predefined scratch variables:
- `P8ZP_SCRATCH_B1` (byte)
- `P8ZP_SCRATCH_REG` (byte)
- `P8ZP_SCRATCH_W1` (word)
- `P8ZP_SCRATCH_W2` (word)
- `P8ZP_SCRATCH_PTR` (word)
- On CX16: virtual registers `cx16.r0`-`cx16.r15` are in zeropage (and their low/high bytes: `cx16.r0L`, `cx16.r0H`, etc.)
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
- For additional temporary storage, allocate regular variables in BSS
## Common 6502 Patterns
### Looping (downto with BNE)
```asm
ldx #count
loop ; do work here
dex
bne loop ; loop while X != 0
```
### Indirect indexed read (table of data)
```asm
ldy #index
lda (ptr),y ; read byte at address stored in zp ptr + Y
```
### 16-bit arithmetic (word add)
```asm
clc
lda word1_lo
adc word2_lo
sta result_lo
lda word1_hi
adc word2_hi
sta result_hi
```
### Calling a Prog8 subroutine from assembly
```asm
jsr p8s_myblock.p8s_mysub
```
### Reading a word variable
```asm
lda p8v_myword ; loads LSB
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
```
+221
View File
@@ -0,0 +1,221 @@
---
name: prog8-coder
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
license: MIT
compatibility: opencode
---
# Prog8 Coder Skill
You are working with **Prog8** source code (`.p8` files) or its Intermediate Representation (`.p8ir` files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a `virtual` target for testing.
Follow ALL the rules below carefully.
## General & Setup
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
- **Compilation outputs**: `*.prg` (program binary), `*.asm` (assembly listing), `*.list` (full listing), `*.p8ir` (IR for VM), `*.vice-mon-list` (Vice debug symbols)
- `.p8ir` files contain the **Intermediate Representation** — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via `prog8c -vm file.p8ir`. Useful for debugging the compiler's code generation path without involving 6502 assembly.
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs)
- **TODO list**: Check `docs/source/todo.rst` to see what features are NOT yet implemented
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
- **`sys` module**: always available, no import needed
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
## Datatypes & Variables
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
- **float requires `%import floats`** at top of file, else compiler errors
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
- `memory(name, size)` returns a `uword` address to a statically reserved block of memory
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable)
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
- **No call stack**: all variables statically allocated. No recursion without manual stack management
- Variables zero-initialized (globals at start, locals on subroutine entry)
- `@shared` marks variables as "used by external code" (assembly), prevents optimization
- `@zp`/`@requirezp`: use sparingly — only for pointers (limited zeropage space)
- Pointer-like typed pointers (`^^type`) support C-style scaled arithmetic; `uword` pointers always treat element as 1 byte
- `&` = untyped address (uword); `&&` = typed pointer
- Available zeropage scratch: `P8ZP_SCRATCH_B1`, `P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, `P8ZP_SCRATCH_PTR` — and cx16 virtual registers R0-R15 on all targets
- Virtual registers (`cx16.r0`–`cx16.r15`): global 16-bit, NOT preserved across calls. R12-R15 may be clobbered by long ops. Save/restore in IRQs with `cx16.save_virtual_registers()`/`cx16.restore_virtual_registers()`
- Math performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
## Recursion & Stack Management
No call stack for variable storage — recursion overwrites locals. To handle it:
1. **CPU hardware stack**: `push()`/`pushw()`/`pushl()`/`pushf()` and `pop()`/`popw()`/`popl()`/`popf()`. Save/restore locals around recursive calls
2. **Software stacks**: `buffers.stack` (uword) / `buffers.smallstack` (ubyte). Both provide `push_b()`/`push_w()` and `pop_b()`/`pop_w()`
3. **Iterative rewrite (preferred)**: Many recursive algorithms work as `repeat` loops with explicit bounds — avoids all stack overhead
## Strings, Arrays & Pointers
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
- **No const pointers** or pointer-to-pointer currently supported
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
- **Pre-allocate buffers**: `str buffer = "." * 50` (empty `""` allocates nothing — `strings.append()` will fail)
- **No reassignment**: can't `buffer = "new text"` after declaration. Use `strings.copy()`/`strings.ncopy()`
- String concat is expensive on 6502. Prefer separate prints over concatenation
- **Efficient buffer iteration**: prefer `ptr++` + `@(ptr)` over `@(buffer + offset)`. Exception: if offset is a `ubyte` (≤ 255), `buffer[offset]` works fine
- `@(ptr)` = peek/poke byte. For words: `peekw`/`pokew`, longs: `peekl`/`pokel`, floats: `peekf`/`pokef`, bools: `peekbool`/`pokebool`
- Use `len(array)` instead of hardcoded sizes
- Array indexing is 0-based: `arr[0]` is first element
- Static memory only — real dynamic allocation impossible, but can emulate with a simple arena allocator over a `memory()` slab
## Logic & Control Flow
- Logical operators (short-circuit, bool only): `and`, `or`, `xor`, `not`
- In `a and b`: if `a` is false, `b` is NOT evaluated
- In `a or b`: if `a` is true, `b` is NOT evaluated
- Important when `b` has side effects
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
- Use `when` with choice blocks instead of multiple `if`
- If-expressions for simple value assignments based on a choice
- `defer` defers statement execution until scope exit
- `goto`, labels, jump lists allowed
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
## Loop Constructs
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
### `for` loop — iterate over a range or array
- Loop variable **must be declared separately** before the `for` statement
- Works with `ubyte`, `byte`, `uword`, `word`, `long`, pointer types (NOT `float`)
- Iterates over ranges (`start to end`), descending ranges (`start downto end`), or arrays/strings
- Optional `step <constant>` for non-unit step sizes
- Loop variable value **after the loop is undefined** — don't rely on it
- Descending loops with `downto` usually produce more efficient 6502 code
```
ubyte i
for i in 20 to 155 {
; body
break ; exit loop
continue ; next iteration
}
; descending
for i in 155 downto 20 {}
for i in 155 to 20 step -1 {}
; iterate over array elements
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
uword num
for num in fib {
...
}
```
### `while` loop — repeat while condition is true
```
while condition {
; body
break
continue
}
```
### `do`-`until` loop — always executes body at least once
```
do {
; body
break
continue
} until condition
```
### `repeat` loop — repeat a fixed number of times (most efficient)
- Most efficient code generation — prefer over `for` when loop variable not needed
- Omit count for infinite loop (still supports `break`)
```
repeat 15 {
; body
break
continue
}
; infinite:
repeat {
; body
break if x==5
}
```
### `unroll` loop — compile-time code duplication
- Not a real loop — duplicates body N times at compile time
- No `break`/`continue` allowed
- Only simple statements (assignments, calls) in body
- Constant iteration count required
```
unroll 80 {
cx16.VERA_DATA0 = 255
}
```
## Subroutines & Return Values
- Symbols public by default; use `private` to restrict to block
- `inline` keyword for subroutines to suggest inlining
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
- Nested subroutines access parent scope variables directly
## Assembly Subroutines (asmsub)
For kernel (ROM) routines or low-level assembly that gets arguments via registers.
- Parameter passing via registers: `@A` (accu), `@X`, `@Y`, `@AX`/`@AY` (16-bit), `@R0`-`@R15` (virtual regs), `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
- Return value: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use)
- Clobbers: `clobbers (A, X, Y)` — list all modified registers
- Parameter names are documentation only — use registers in assembly, not parameter names
- Create symbolic aliases at assembly start: `x1 = cx16.r0`, `x1_lo = cx16.r0L`
- Example:
```prog8
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
%asm {{
x1 = cx16.r0
x2 = cx16.r1
lda x1
}}
}
```
- Symbol prefixes in assembly: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
- Fully qualified: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
- Within a `.proc`, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
- Split word arrays: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
- CX16 target only: use 65C02 instructions (STZ etc). Others: 6502 only
- Assembly syntax: 64tass assembler. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
- Instructions like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`, not just `rol`
- Anonymous labels: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
## Standard Library
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
- String functions return useful lengths — capture them: `len = strings.copy(dest, src)`, `len = strings.append(buf, text)`, `len = strings.upper(mystr)`
## Syntax & Formatting
- Hex: `$FF` (not `0xFF`); Binary: `%1010` (not `0b1010`). Underscores for readability: `25_000_000`
- 4-digit hex `$0000` = uword. No type suffixes (no `0L`). Cast: `expr as type`
- Augmented assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
- **`;` starts a comment to end of line** — NOT a statement separator. One statement per line
- **No `elif`**: use nested `else { if ... }`
- Type casting: `expression as type` (e.g., `bytevar as word`). `as` has very low precedence (lower than arithmetic)
- **No automatic type widening**: `byte*byte=byte` (overflow possible!). Cast explicitly
- **No block scope**: `for`/`if` don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level
- **No bare `{ }` blocks** like C/Java
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
- Avoid `globals.XXXX` — move constants closer to where they're used
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
- **No block scope** — variables in `if`/`for`/`repeat` blocks are hoisted to subroutine level
- **No reassigning strings** after declaration — use `strings.copy()`/`strings.ncopy()`
- **Pre-allocate string buffers** — `""` allocates nothing
+45
View File
@@ -0,0 +1,45 @@
# EditorConfig for Prog8 projects
# Ensures consistent formatting across editors and team members
# See https://editorconfig.org
root = true
# All files
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Prog8 source files (.p8)
[*.p8]
indent_style = space
indent_size = 4
# Assembly files (.asm) - 64tass syntax
[*.asm]
indent_style = space
indent_size = 4
# Kotlin compiler source files
[*.kt]
indent_style = space
indent_size = 4
trim_trailing_whitespace = false
# Documentation files
[*.md]
trim_trailing_whitespace = false
[*.rst]
trim_trailing_whitespace = false
# Gradle build files
[*.gradle.kts]
indent_style = space
indent_size = 4
# Git ignore files
[.gitignore]
indent_style = space
indent_size = 2
+3
View File
@@ -3,6 +3,8 @@
.idea/developer-tools.xml
.idea/usage.statistics.xml
.idea/shelf/
.junie/
.junie/memory/
build/
dist/
output/
@@ -22,6 +24,7 @@ parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
compiler/src/prog8/buildversion/*
languageServer/src/prog8/buildversion/*
*.py[cod]
*.egg
*.egg-info
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JunieProjectTechnologies">
<option name="explicitlyEnabledTechs">
<set>
<option value="junie.python" />
</set>
</option>
</component>
</project>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
<option name="name" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>
+5 -5
View File
@@ -7,13 +7,13 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
<option name="apiVersion" value="2.3" />
<option name="languageVersion" value="2.3" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />
<option name="version" value="2.3.20" />
</component>
</project>
</project>
+10 -10
View File
@@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+14
View File
@@ -0,0 +1,14 @@
<component name="libraryTable">
<library name="github.irmen.ksim65" type="repository">
<properties maven-id="com.github.irmen:ksim65:v2.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.0/ksim65-v2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+7 -7
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-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.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
-1
View File
@@ -2,7 +2,6 @@
<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" />
+5
View File
@@ -0,0 +1,5 @@
---
description: Startup workflow to load project context
---
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
+5
View File
@@ -0,0 +1,5 @@
# Agent Instructions
- The file `AGENTS.md` MUST be loaded as the very first step of every session, if it isn't already loaded by the system.
- Follow all project-specific guidelines and information provided in `AGENTS.md`.
- Ensure all automated tasks and code generations adhere to the language rules and architectural decisions described in `AGENTS.md`.
+31
View File
@@ -0,0 +1,31 @@
{
"context": {
"fileName": "AGENTS.md"
},
"$version": 3,
"permissions": {
"allow": [
"Bash(prog8c *)",
"Bash(gradle *)",
"Bash(mkdir *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(find *)",
"Bash(sed *)",
"Bash(grep *)",
"Bash(xargs *)",
"Bash(wc *)",
"Bash(diff *)",
"Bash(paste *)",
"Bash(echo *)",
"Bash(cp *)",
"Bash(time)",
"Bash(kotlinc *)",
"Bash(python3 *)",
"Bash(jar *)",
"Bash(od *)",
"Bash(xxd *)",
"Bash(gcc *)"
]
}
}
+336
View File
@@ -0,0 +1,336 @@
# CRITICAL RULES - MUST FOLLOW
## RESPONSES
- Keep responses concise and to the point - unless the user asks otherwise
## PLANNING MODE
- Always ask clarifying questions
- Never assume design, tech stack or features
- Use deep-dive sub-agents to assist with research
- Use deep-dive sub-agents to review the different aspects of your plan before presenting to the user
## CHANGE / EDIT MODE
- Never implement features yourself when possible - use sub-agents!
- Identify changes from the plan that can be implemented in parallel, and use sub-agents to implement the features efficiently
- When using sub-agents to implement features, act as a coordinator only
- Use the best model for the task - premium models for complex tasks (like coding) and mid-tier models for simpler tasks, like documentation
- After completing features (large or small), always run commands like lint, type check and next build to check code quality
## Project Overview
- This project is a compiler for the Prog8 programming language.
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
- The compiler has a 6502 code generator backend, and an IR code generator.
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
- Prog8 source files have .p8 extension – these are *not* LUA or PICO-8 source files in this case!
- Prog8 source files are a "module" that can contain one or more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
- Kotlin version 2.3 is used for the compiler implementation.
- Java 17 is used as Java runtime version.
- ANTLR4 version 4.13 is used for the parser implementation.
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
## Compilation Flow (High-Level)
```
Source → parseMainModule() → processAst() → optimizeAst() → postprocessAst()
→ Simple AST → Code Generator → Assembly → .prg
```
**Key phases in `compiler/src/prog8/compiler/Compiler.kt`:**
- `parseMainModule()` - Import modules, parse to Compiler AST
- `processAst()` - Semantic analysis, constant folding, type casting, validation
- `optimizeAst()` - Dead code elimination, inlining, statement optimization
- `postprocessAst()` - Memory layout, final transformations
- Code generation – Simple AST → IR or 6502 assembly
**Understanding this order is important:** For example, `ConstantIdentifierReplacer` runs during `processAst()`, so by the time `AstChecker` runs at the end of that phase, identifier references have already been replaced with their actual values.
### Target Differences
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
- When writing or understanding assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
## DEBUGGING TIP: Use `-noopt` to isolate problems
When investigating a possible code generation problem (both IR and 6502), **FIRST try compiling the program with the `-noopt` switch** to disable most of the compiler optimizations.
- **Problem gone with `-noopt`**: Issue is in **optimization phases** (`optimizeAst()`, `UnusedCodeRemover`, `Inliner`, etc.)
- **Problem persists with `-noopt`**: Issue is in **parsing, semantic analysis, symboltable, or the regular code generation path**.
This way you can determine if the problem is caused by a faulty optimization step, or just occurs in the regular code generation path.
## DEBUGGING TIP: Use `-compareir` to see what changed
When investigating optimization-related issues or tracking regressions:
```bash
# Compile without optimizations (baseline)
prog8c -target virtual -noopt -out dir program.p8
# Compile with optimizations and compare
prog8c -target virtual -compareir dir/program_noopt.p8ir program.p8
```
**Output shows:**
- Instruction/chunk/register count changes with percentages
- First 10 actual instruction differences
- Helps identify which optimization transformed the code
## DEBUGGING TIP: Use `-vmtrace` to trace execution
When debugging VM execution or control flow issues:
```bash
prog8c -target virtual -vm program.p8ir -vmtrace
prog8c -target virtual -emu -vmtrace program.p8
```
**Output format:** `[chunkName:instructionIndex] instruction`
- Shows each executed IR instruction with location
- Useful for understanding control flow and finding where execution diverges
- **Only works on virtual target**
## Typical debugging workflow:
1. **Quick check:** `-check` for syntax errors
2. **Isolate:** `-noopt` to determine if problem is optimizer-related
3. **Compare:** `-compareir` to see what instructions changed
4. **Trace:** `-vmtrace` to watch actual execution flow
5. **Deep dive:** `-printast1` / `-printast2` for compiler internals
## IMPORTANT: SymbolTable cachedFlat cache
The `SymbolTable` class has a **cached `flat` property** for fast symbol lookups.
**CRITICAL:** If you modify the AST **after** SymbolTable construction, you **MUST** call `symbolTable.resetCachedFlat()` or lookups will fail!
## IMPORTANT: Virtual Target (IR Codegen) Issues
For problems that **ONLY occur with the 'virtual' target**, **ONLY modify these modules**:
- `codeGenIntermediate` - IR code generator
- `intermediate` - IR representation and file I/O
- `virtualmachine` - VM that executes IR code
**DO NOT modify** `compilerAst`, `simpleAst`, `codeGenCpu6502`, etc. The IR codegen has its own separate handling for symbol tables, AST transformations, and unused code removal.
## IMPORTANT: Compiler Crashes Must Be Fixed in the Compiler
A Kotlin/Java crash in the compiler itself (as opposed to a compilation error in the user's program) indicates a bug in the compiler code. **Such crashes should NEVER be worked around by modifying the Prog8 source file that triggered them.**
Instead, always fix the bug in the compiler's Kotlin code. The user's program is correct - the compiler needs to handle it properly.
Steps when encountering a compiler crash:
1. Create a minimal reproduction case of the Prog8 source that triggers the crash
2. Debug the compiler to find the root cause in the Kotlin code
3. Fix the compiler bug, not the user program
## CRITICAL: NO FORMATTING
- DO NOT change indentation and formatting of lines that are not being modified. NEVER run formatters (black, ruff, prettier, etc.) after edits,
- .editorconfig handles basic formatting (indentation, line endings, whitespace)
- Make ONLY the requested changes, touch nothing else
- **NO EMOJI in user documentation**. Do not use emoji or decorative unicode symbols in documentation files. Functional unicode symbols are acceptable when they serve a clear purpose (e.g., → for arrows, ± for plus-minus, × for multiplication, ° for degrees). Avoid decorative emoji like ❌ ✅ ⚠️ 🎉 etc.
### Code Style Guidelines
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
## Prog8 language information
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
## Project Module Descriptions
- `compiler` - Main compiler entrypoint (src/prog8/CompilerMain.kt)
- `compilerAst` - Complex AST where most optimizations run
- `simpleAst` - Simplified AST used by code generator backends
- `parser` - ANTLR4 parser implementation
- `codeCore` - Core code generation utilities
- `codeGenCpu6502` - 6502 assembly code generator backend
- `codeGenIntermediate` - Intermediate representation (IR) code generator
- `codeGenVirtual` - Virtual machine code generator backend
- `codeOptimizers` - Optimization passes
- `intermediate` - IR components
- `virtualmachine` - VM that executes IR code
- `languageServer` - Language Server Protocol implementation
- `docs` - Documentation files
- `examples` - Example Prog8 programs
## Key Information
- never read the files and directories that are ignored via the .aiignore and .gitignore files (EXCEPT when it is a folder for agent config such as .junie, .opencode, .qwen or .agent)
- never perform any git source control write/update/add/commit/branch operations. Read and status operations are allowed.
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
## Junie Agent Commitments
These instructions apply specifically to the Junie agent:
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
# Dev environment tips
## Development Workflows
**1. Testing your own Prog8 programs** (no compiler changes):
- No rebuild needed - just run `prog8c` directly
- Edit your `.p8` file → compile/run → check stdout output
- Example: `prog8c -target virtual -emu myprogram.p8`
**2. Testing compiler or standard library changes**:
- Rebuild required after every change to `.kt`, `.p8`, or `.asm` files in the compiler project
- Workflow: edit source → `gradle installdist installshadowdist` → test with `prog8c`
- Example: fix bug in `CodeGen6502.kt` → rebuild → `prog8c -target cx16 test.p8`
## Building and Installing the Compiler
- use the system installed gradle command instead of the gradle wrapper.
### Quick compile check (NO tests)
- **After changing compiler Kotlin source (.kt files)**: Use `gradle :compiler:compileKotlin --console=plain` to quickly check for syntax/compile errors
- This compiles the compiler but **skips running tests**, this is much faster than `gradle build`
- **Note:** This does NOT install the compiler - use `gradle installdist installshadowdist --console=plain` after to actually use your changes
### When you MUST rebuild AND reinstall the compiler
**After ANY change to:**
1. **Kotlin compiler source code** (`.kt` files in any module, or the `.g4` grammar file)
2. **Standard library Prog8 files** (`compiler/res/prog8lib/**/*.p8`)
3. **Standard library assembly files** (`compiler/res/prog8lib/**/*.asm`)
**Run:** `gradle installdist installshadowdist --console=plain`
**Why:** The standard library files (.p8 and .asm) are embedded into the compiler JAR during the build. Changes to these files are NOT picked up by simply running `prog8c` - you MUST rebuild and reinstall for your changes to take effect.
**Without this step, your changes will NOT be reflected when running `prog8c`!**
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
### When to run full build with tests
- **Before committing changes**: Always run `gradle build` to ensure all tests pass
- **After major refactoring**: Run `gradle build` to catch regressions
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
### Build command summary
| Command | When to use | Time |
|---------|-------------|------|
| `gradle :compiler:compileKotlin --console=plain` | Quick syntax check (no tests) | ~10-20s |
| `gradle installdist installshadowdist --console=plain` | After compiler changes (to use them) | ~10-20s |
| `gradle build --console=plain` | Before commits, after major changes | ~45-60s |
| `gradle test --tests "*Name*" --console=plain` | Run specific tests | ~5-30s |
**Note:** Use system `gradle` command, not wrapper. Run `gradle clean` if you suspect stale artifacts.
## Using the Compiler (prog8c)
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
- **the `-check` switch performs a quick syntax/semantic check only; it will NOT produce any output files (no .prg, .asm, etc.)**. Use it only for fast error checking during development.
- **the `-noopt` switch DISABLES all optimizations** - useful for debugging to determine if a problem is caused by the optimizer. **Optimizations are ENABLED by default** (no flag needed).
- **prog8c uses single-dash command line options** (e.g., `-target`, `-noopt`, `-check`), NOT double-dash (`--target` is invalid).
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
### Compilation Output Files
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
- `*.asm` - Generated assembly code from the Prog8 source
- `*.list` - Generated full assembly listing file from the Prog8 source
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
### Execution Examples
- `prog8c -target targetname input.p8` - Compile a Prog8 source file "input.p8" for the given target (cx16, c64, pet32, c128, virtual)
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
## Testing and Verification
- Never assume your changes simply work, always test!
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
### Automated Tests (gradle)
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
- `gradle build --console=plain` - Full build including all tests (about 50s)
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
- `gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
| Pattern | Example | Works? |
|---------|---------|---------------|
| **Full class name** | `--tests "prog8tests.compiler.TestOptimization"` | **USE THIS** |
| Wildcard at END | `--tests "prog8tests.compiler.Test*"` | Yes |
| Wildcard on package | `--tests "prog8tests.compiler.*"` | Yes |
| **Wildcard at START** | `--tests "*TestOptimization"` | FAILS |
| Test description | `--tests "*Optimization*inline*"` | FAILS |
**Why the restrictions?** Gradle's `--tests` filter matches **fully qualified class names only**. Wildcards work as **suffixes** (e.g., `Test*`) but NOT as prefixes (e.g., `*Test`). KoTest test names like `"inline multi-value void"` are _descriptions_, not method names, so you cannot filter by them.
**To find failing tests after a run:**
```bash
# Check XML results (reliable)
grep "<failure" compiler/build/test-results/test/*.xml
# Or check HTML report (most reliable)
cat compiler/build/reports/tests/test/index.html
# For test compilation errors
gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
```
**Additional notes:**
- Unit tests use KoTest (FunSpec style)
- Tests are in the "compiler" module's `test` directory (and some other modules)
- Test config is in root `build.gradle.kts`; tests run in parallel
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit`
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
### Manual Verification & Emulators
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
```prog8
%encoding iso
%import textio
main { sub start() { txt.iso(); txt.print("PASS\n") } }
```
**IMPORTANT: Always use `sys.poweroff_system()` to exit the CX16 emulator cleanly!** Add `sys.poweroff_system()` at the end of your main program block - this exits x16emu automatically in most cases.
**Note:** The `sys` module is always available, there is no need to import it ever.
**Commodore 64 (x64sc)**: `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator. Ignore any errors and warnings, because the emulator doesn't produce any output on STDOUT.
### actual 6502 CPU simulation tests
For high-fidelity functional verification of generated 6502 code without a full emulator, you can use the `ksim65` simulator in your unit tests. This allows executing the compiled machine code and asserting on memory or register states.
- Use the `simulate()` extension function on a `CompilationResult`.
- See `prog8tests.codegeneration.TestExecution6502` for examples.
- Both 6502 and 65C02 CPUs can be simulated.
- The simulator supports capturing serial output and handling hardware reset/poweroff signals.
- There are various assertion helper methods to test on the state of CPU registers, memory contents, etc.
- Using the simulator is much faster and way more controllable than using a full emulator to run the code.
- The simulator DOES NOT represent an actual real world machine and has VERY LIMITED hardware devices so it can generally not be used to run full example prorams on.
## Git Operations for File Moves/Deletes
**When renaming or moving git-tracked files, ALWAYS use `git mv`:**
```bash
# CORRECT - preserves git history
git mv old/path/File.kt new/path/File.kt
# WRONG - git sees this as delete + add (loses history)
mv old/path/File.kt new/path/File.kt
```
**When deleting git-tracked files, ALWAYS use `git rm`:**
```bash
# CORRECT - properly stages the deletion
git rm path/to/File.kt
# WRONG - git sees this as unstaged deletion
rm path/to/File.kt
```
**Why this matters:** `git mv` and `git rm` properly stage the changes and preserve file history. Plain `mv`/`rm` requires git to detect renames heuristically, which may not always work correctly.
## TODO Items
The file `docs/source/todo.rst` contains a comprehensive list of things that still have to be fixed, implemented, or optimized. **Use this to understand what features are NOT yet available** in the compiler or Prog8 language: if a user asks for something that's on the TODO list, you'll know it's not implemented yet and can explain the limitation.
+11 -3
View File
@@ -1,12 +1,20 @@
# super simple Makefile to lauch the main gradle targets to build and/or test the prog8 compiler
# super simple Makefile to launch the main gradle targets to build and/or test the prog8 compiler
.PHONY: all test
# Use system gradle if available, otherwise fall back to gradlew wrapper
# Windows uses 'where' and gradlew.bat, Unix uses 'command -v' and ./gradlew
ifeq ($(OS),Windows_NT)
GRADLE = $(shell where gradle >nul 2>&1 && echo gradle || echo gradlew.bat)
else
GRADLE = $(shell command -v gradle >/dev/null 2>&1 && echo gradle || echo ./gradlew)
endif
all:
gradle installdist installshadowdist
$(GRADLE) installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
test:
gradle build
$(GRADLE) build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
+8
View File
@@ -65,6 +65,7 @@ What does Prog8 provide?
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, long integers, floats, strings)
- Structs and typed pointers
- 2D arrays (`matrix[row][col]` syntax)
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- tight control over Zeropage usage
@@ -205,3 +206,10 @@ Here is a performance comparison with various C compilers for the 6502/C64 so yo
an idea of how Prog8 stacks up.
[comparison](benchmark-c/benchmark.md)
Agent context instructions
--------------------------
[Agent context instructions](AGENTS.md)
-24
View File
@@ -1,24 +0,0 @@
<?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>
-65
View File
@@ -1,65 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("application")
kotlin("jvm")
}
val serverMainClassName = "prog8lsp.MainKt"
val applicationName = "prog8-beanshell"
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")
}
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.installDist {
finalizedBy("fixFilePermissions")
}
tasks.build {
finalizedBy("installDist")
}
Binary file not shown.
@@ -1,48 +0,0 @@
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
@@ -1,23 +0,0 @@
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
))
}
+16 -16
View File
@@ -113,14 +113,14 @@ btree {
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
pushw(r)
count_node(r.left)
r = sys.popw()
r = popw()
}
if r.right!=0 {
sys.pushw(r)
pushw(r)
count_node(r.right)
r = sys.popw()
r = popw()
}
}
}
@@ -179,15 +179,15 @@ btree {
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
pushw(r)
process_tree(r.left)
r = sys.popw()
r = popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
pushw(r)
process_tree(r.right)
r = sys.popw()
r = popw()
}
}
}
@@ -200,18 +200,18 @@ btree {
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
pushw(r)
push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
depth = pop()
r = popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
pushw(r)
push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
depth = pop()
r = popw()
}
}
}
+4 -4
View File
@@ -35,12 +35,12 @@ queens {
if could_place(row, col) {
board[row] = col
; we need to save the local variables row and col.
sys.push(row)
sys.push(col)
push(row)
push(col)
continue_running = place_queen(row + 1)
; restore the local variables after the recursive call.
col = sys.pop()
row = sys.pop()
col = pop()
row = pop()
board[row] = 0
if not continue_running
+74 -7
View File
@@ -1,11 +1,9 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "2.3.0"
kotlin("jvm") version "2.3.20"
}
allprojects {
@@ -14,12 +12,13 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
kotlin {
compilerOptions {
freeCompilerArgs = listOf()
jvmTarget = JvmTarget.JVM_11
jvmTarget = JvmTarget.JVM_17
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
}
@@ -32,7 +31,75 @@ allprojects {
}
java {
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
}
}
// ============================================================================
// Common Configuration for All Subprojects
// ============================================================================
// This avoids duplication in each module's build.gradle.kts
// ============================================================================
subprojects {
// Common dependency exclusions for all subprojects
// Note: antlr4 exclusion is done per-module since parser module needs it
configurations.all {
exclude(group = "com.ibm.icu", module = "icu4j")
}
// Common test dependencies via Kotest BOM (Bill of Materials)
// This manages all Kotest module versions centrally
dependencies {
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
// implementation("com.github.irmen:ksim65:v2.0")
}
// Common sourceSets configuration
sourceSets {
main {
java.srcDir("${project.projectDir}/src")
resources.srcDir("${project.projectDir}/res")
}
test {
java.srcDir("${project.projectDir}/test")
}
}
// Common test configuration
tasks.withType<Test>().configureEach {
// Enable JUnit 5 (required for Kotest)
useJUnitPlatform()
// Enable concurrent test execution for Kotest
// Set parallelism to number of CPU cores
jvmArgs("-Dkotest.framework.parallelism=${Runtime.getRuntime().availableProcessors()}")
// Disable Kotest autoscan warning
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
// Use 50% of available processors to avoid over-subscription
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
// Allow filtering tests via -PtestFilter="*TestName*" from command line
// Example: gradle test -PtestFilter="*TestLookup*"
val testFilter = project.findProperty("testFilter")?.toString()
if(testFilter != null) {
filter {
includeTestsMatching(testFilter)
}
}
// Show test results - only show failures to reduce noise
testLogging {
events("failed")
// Show full exception details for failures
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}
}
+4 -14
View File
@@ -1,22 +1,12 @@
plugins {
kotlin("jvm")
`java-test-fixtures`
}
dependencies {
// should have no dependencies to other modules
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
sourceSets {
main {
java {
srcDir("${project.projectDir}/src")
}
resources {
srcDir("${project.projectDir}/res")
}
}
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
// note: there are no unit tests in this module!
+4 -1
View File
@@ -4,11 +4,14 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<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="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
</module>
+20 -1
View File
@@ -29,4 +29,23 @@ fun Path.sanitize(): Path {
} catch (iox: IOException) {
throw FileSystemException(this.toFile()).also { it.initCause(iox) }
}
}
}
object SymbolNames {
/**
* Strips Prog8 symbol prefixes (p8b_, p8t_, p8s_, p8v_, p8l_, p8c_) from a scoped name.
* Example: "p8b_plane.p8t_Point" -> "plane.Point"
* Used to compare names across different contexts where prefixes may or may not be applied.
* See https://github.com/irmen/prog8/issues/198
*/
fun stripPrefixes(scopedName: String): String {
return scopedName.split('.')
.map { part ->
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
part.drop(4)
else part
}
.joinToString(".")
}
}
+116 -103
View File
@@ -30,40 +30,48 @@ private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, Ba
class FSignature(val pure: Boolean, // does it have side effects?
val returnType: BaseDataType?,
val returnTypes: Array<BaseDataType>,
vararg val parameters: FParam) {
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
val returns: ReturnConvention = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
val returns: ReturnConvention
if(returnTypes.isEmpty())
returns = ReturnConvention(null, null)
else if(returnTypes.size==1) {
val returnType = returnTypes[0]
returns = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
}
}
}
} else {
// note: this path is currently never hit because the builtin functions with multiple return values have been handled in specialized optimized codegens already earlier.
TODO("multiple return types from builtin function")
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// One parameter goes via register/registerpair.
// One parameter goes via register/registerpair (except longs)
// 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).
val paramConv = when(val paramType = actualParamTypes[0]) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R14R15, false)
BaseDataType.LONG -> ParamConvention(paramType, null, true)
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
@@ -89,96 +97,101 @@ class FSignature(val pure: Boolean, // does it have side effects?
}
val BuiltinFunctions: Map<String, FSignature> = mapOf(
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"prog8_lib_copylong" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"prog8_lib_copyfloat" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
"sqrt__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"swap" to FSignature(false, null, FParam("var1", *NumericDatatypes + BaseDataType.BOOL), FParam("var2", *NumericDatatypes + BaseDataType.BOOL)),
"swap__byte" to FSignature(false, null, FParam("var1", BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL), FParam("var2",BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL)),
"swap__word" to FSignature(false, null, FParam("var1", BaseDataType.WORD, BaseDataType.UWORD), FParam("var2",BaseDataType.WORD, BaseDataType.UWORD)),
"swap__long" to FSignature(false, null, FParam("var1", BaseDataType.LONG), FParam("var2",BaseDataType.LONG)),
"swap__float" to FSignature(false, null, FParam("var1", BaseDataType.FLOAT), FParam("var2",BaseDataType.FLOAT)),
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
"lsb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, null),
"rrestore" to FSignature(false, null),
"memory" to FSignature(true, BaseDataType.UWORD, FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"callfar" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
"call" to FSignature(false, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"setlsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, emptyArray(), FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
"prog8_lib_stringcompare" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"abs" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"sqrt__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
"divmod" to FSignature(false, arrayOf(BaseDataType.UNDEFINED, BaseDataType.UNDEFINED), FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD)),
"divmod__ubyte" to FSignature(false, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE)),
"divmod__uword" to FSignature(false, arrayOf(BaseDataType.UWORD, BaseDataType.UWORD), FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD)),
"divmod__byte" to FSignature(false, arrayOf(BaseDataType.BYTE, BaseDataType.BYTE), FParam("dividend", BaseDataType.BYTE), FParam("divisor", BaseDataType.BYTE)),
"divmod__word" to FSignature(false, arrayOf(BaseDataType.WORD, BaseDataType.WORD), FParam("dividend", BaseDataType.WORD), FParam("divisor", BaseDataType.WORD)),
"lmh" to FSignature(true, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"lsb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"msb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"lsw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, emptyArray(), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, emptyArray()),
"rrestore" to FSignature(false, emptyArray()),
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
)
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"divmod", "divmod__ubyte", "divmod__uword",
"swap", "swap__byte", "swap__word", "swap__long", "swap__float"
"rol", "ror", "rol2", "ror2"
)
val SimpleBuiltinFunctions = setOf(
"msb", "lsb", "msw", "lsw",
"mkword", "mklong", "mklong2",
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
@@ -34,6 +34,7 @@ class CompilationOptions(val output: OutputType,
var ignoreFootguns: Boolean = false,
var outputDir: Path = Path(""),
var quiet: Boolean = false,
var profilingInstrumentation: Boolean = false,
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
@@ -42,5 +43,81 @@ class CompilationOptions(val output: OutputType,
companion object {
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
fun builder(compTarget: ICompilationTarget) = Builder(compTarget)
class Builder(private val compTarget: ICompilationTarget) {
private var output: OutputType = compTarget.defaultOutputType
private var launcher: CbmPrgLauncherType = CbmPrgLauncherType.NONE
private var zeropage: ZeropageType = ZeropageType.DONTUSE
private var zpReserved: List<UIntRange> = emptyList()
private var zpAllowed: List<UIntRange> = AllZeropageAllowed
private var floats: Boolean = false
private var noSysInit: Boolean = false
private var romable: Boolean = false
private var compilerVersion: String = "unknown"
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
private var warnSymbolShadowing: Boolean = false
private var warnImplicitTypeCast: Boolean = false
private var optimize: Boolean = false
private var asmQuiet: Boolean = false
private var asmListfile: Boolean = false
private var includeSourcelines: Boolean = false
private var dumpVariables: Boolean = false
private var dumpSymbols: Boolean = false
private var experimentalCodegen: Boolean = false
private var varsHighBank: Int? = null
private var varsGolden: Boolean = false
private var slabsHighBank: Int? = null
private var slabsGolden: Boolean = false
private var breakpointCpuInstruction: String? = null
private var ignoreFootguns: Boolean = false
private var outputDir: Path = Path("")
private var quiet: Boolean = false
private var profilingInstrumentation: Boolean = false
private var symbolDefs: Map<String, String> = emptyMap()
fun output(output: OutputType) = apply { this.output = output }
fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher }
fun zeropage(zeropage: ZeropageType) = apply { this.zeropage = zeropage }
fun zpReserved(zpReserved: List<UIntRange>) = apply { this.zpReserved = zpReserved }
fun zpAllowed(zpAllowed: List<UIntRange>) = apply { this.zpAllowed = zpAllowed }
fun floats(floats: Boolean) = apply { this.floats = floats }
fun noSysInit(noSysInit: Boolean) = apply { this.noSysInit = noSysInit }
fun romable(romable: Boolean) = apply { this.romable = romable }
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
fun asmQuiet(asmQuiet: Boolean) = apply { this.asmQuiet = asmQuiet }
fun asmListfile(asmListfile: Boolean) = apply { this.asmListfile = asmListfile }
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden }
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
fun build(): CompilationOptions {
return CompilationOptions(
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
profilingInstrumentation, symbolDefs
)
}
}
}
}
+441
View File
@@ -0,0 +1,441 @@
package prog8.code.core
import java.util.*
/**
* Base data types supported by the Prog8 compiler.
* These represent the fundamental types that can be used in Prog8 programs.
*/
enum class BaseDataType {
UBYTE, // pass by value 8 bits unsigned
BYTE, // pass by value 8 bits signed
UWORD, // pass by value 16 bits unsigned
WORD, // pass by value 16 bits signed
LONG, // pass by value 32 bits signed
FLOAT, // pass by value machine dependent
BOOL, // pass by value bit 0 of an 8-bit byte
STR, // pass by reference
ARRAY, // pass by reference, subtype is the element type
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
UNDEFINED;
fun largerSizeThan(other: BaseDataType) =
when {
this == other -> false
this.isByteOrBool -> false
this.isWord -> other.isByteOrBool
this == LONG -> other.isByteOrBool || other.isWord
this == STR && other == UWORD || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
fun equalsSize(other: BaseDataType) =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
else -> false
}
}
// ============================================================================
// BaseDataType Extension Properties
// ============================================================================
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
val BaseDataType.isUnsignedByte get() = this == BaseDataType.UBYTE
val BaseDataType.isSignedByte get() = this == BaseDataType.BYTE
val BaseDataType.isBool get() = this == BaseDataType.BOOL
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
val BaseDataType.isUnsignedWord get() = this == BaseDataType.UWORD
val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
/**
* Interface for types that can be used as subtypes in DataType.
* Primarily used for struct types.
*/
interface ISubType {
val scopedNameString: String
fun memsize(sizer: IMemSizer): Int
fun sameas(other: ISubType): Boolean
fun getFieldType(name: String): DataType?
}
/**
* Represents a complete data type in Prog8, including base type and optional subtype.
*
* DataType is immutable after construction. Use the companion object factory methods
* to create instances.
*
* @property base The base data type (e.g., UBYTE, WORD, ARRAY, POINTER)
* @property sub The subtype for arrays and strings (e.g., UBYTE for byte arrays)
* @property subType The structured subtype for pointers and struct instances
* @property subTypeFromAntlr Deferred subtype resolution from parser
*/
class DataType private constructor(
val base: BaseDataType,
val sub: BaseDataType?,
var subType: ISubType?,
var subTypeFromAntlr: List<String>? = null
) {
init {
when {
base.isPointerArray -> {
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
else -> {
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
"sub and subtype can't both be set"
}
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
}
override fun hashCode(): Int = Objects.hash(base, sub, subType)
fun setActualSubType(actualSubType: ISubType) {
subType = actualSubType
subTypeFromAntlr = null
}
// ============================================================================
// Companion Object - Factory Methods
// ============================================================================
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType = if(dt.isBasic)
DataType(BaseDataType.POINTER, dt.base, null)
else
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
}
// ============================================================================
// DataType Methods
// ============================================================================
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
if (isArray) {
if (msb || isSplitWordArray)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
}
if (subType != null)
return pointer(this)
return UWORD
}
}
fun dereference(): DataType {
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type ${this}"}
return when {
isUnsignedWord -> forDt(BaseDataType.UBYTE)
sub!=null -> forDt(sub)
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
else -> throw IllegalArgumentException("cannot dereference this pointer type")
}
}
override fun toString(): String = when(base) {
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.BOOL -> "bool[]"
BaseDataType.FLOAT -> "float[]"
BaseDataType.BYTE -> "byte[]"
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
BaseDataType.WORD -> "word[] (split)"
BaseDataType.UWORD -> "uword[] (split)"
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
}
BaseDataType.STRUCT_INSTANCE -> {
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
}
else -> base.name.lowercase()
}
fun sourceString(): String = when (base) {
BaseDataType.BOOL -> "bool"
BaseDataType.UBYTE -> "ubyte"
BaseDataType.BYTE -> "byte"
BaseDataType.UWORD -> "uword"
BaseDataType.WORD -> "word"
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "str"
BaseDataType.POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}"
subType!=null -> "^^${subType!!.scopedNameString}"
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
else -> "?????"
}
}
BaseDataType.STRUCT_INSTANCE -> {
when {
sub!=null -> sub.name.lowercase()
subType!=null -> subType!!.scopedNameString
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
else -> "?????"
}
}
BaseDataType.ARRAY_POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}["
subType!=null -> "^^${subType!!.scopedNameString}["
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
else -> "????? ["
}
}
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
BaseDataType.UWORD -> "@nosplit uword["
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
BaseDataType.UWORD -> "uword["
BaseDataType.WORD -> "word["
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
}
/**
* Check if this type is assignable to the given target type (perhaps via a typecast)
* without loss of precision.
*/
infix fun isAssignableTo(targetType: DataType) =
when(base) {
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
targetType.isPointer -> this.isUnsignedWord || this == targetType
else -> false
}
}
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
BaseDataType.ARRAY_POINTER -> false
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
/**
* Returns the memory size in bytes.
* Note: for pointer types, size() doesn't return the size of the pointer itself
* but the size of the thing it points to.
*/
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
memsizer.memorySize(sub)
} else if(subType!=null) {
subType!!.memsize(memsizer)
} else {
memsizer.memorySize(base)
}
// ============================================================================
// DataType Properties
// ============================================================================
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
val isUndefined = base == BaseDataType.UNDEFINED
val isByte = base.isByte
val isUnsignedByte = base == BaseDataType.UBYTE
val isSignedByte = base == BaseDataType.BYTE
val isByteOrBool = base.isByteOrBool
val isWord = base.isWord
val isUnsignedWord = base == BaseDataType.UWORD
val isSignedWord = base == BaseDataType.WORD
val isInteger = base.isInteger
val isWordOrByteOrBool = base.isWordOrByteOrBool
val isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isSignedInteger = isSigned && isInteger
val isUnsignedInteger = isUnsigned && isInteger
val isArray = base.isArray
val isPointer = base.isPointer
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
val isPointerToWord = base.isPointer && sub?.isWord==true
val isStructInstance = base.isStructInstance
val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
}
+8 -390
View File
@@ -1,388 +1,5 @@
package prog8.code.core
import java.util.*
enum class BaseDataType {
UBYTE, // pass by value 8 bits unsigned
BYTE, // pass by value 8 bits signed
UWORD, // pass by value 16 bits unsigned
WORD, // pass by value 16 bits signed
LONG, // pass by value 32 bits signed
FLOAT, // pass by value machine dependent
BOOL, // pass by value bit 0 of an 8-bit byte
STR, // pass by reference
ARRAY, // pass by reference, subtype is the element type
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
UNDEFINED;
fun largerSizeThan(other: BaseDataType) =
when {
this == other -> false
this.isByteOrBool -> false
this.isWord -> other.isByteOrBool
this == LONG -> other.isByteOrBool || other.isWord
this == STR && other == UWORD || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
fun equalsSize(other: BaseDataType) =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
else -> false
}
}
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
interface ISubType {
val scopedNameString: String
fun memsize(sizer: IMemSizer): Int
fun sameas(other: ISubType): Boolean
fun getFieldType(name: String): DataType?
}
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) {
init {
when {
base.isPointerArray -> {
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
else -> {
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
"sub and subtype can't both be set"
}
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
}
override fun hashCode(): Int = Objects.hash(base, sub, subType)
fun setActualSubType(actualSubType: ISubType) {
subType = actualSubType
subTypeFromAntlr = null
}
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType = if(dt.isBasic)
DataType(BaseDataType.POINTER, dt.base, null)
else
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
}
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
if (isArray) {
if (msb || isSplitWordArray)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
}
if (subType != null)
return pointer(this)
return UWORD
}
}
fun dereference(): DataType {
require(isPointer || isUnsignedWord)
return when {
isUnsignedWord -> forDt(BaseDataType.UBYTE)
sub!=null -> forDt(sub)
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
else -> throw IllegalArgumentException("cannot dereference this pointer type")
}
}
override fun toString(): String = when(base) {
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.BOOL -> "bool[]"
BaseDataType.FLOAT -> "float[]"
BaseDataType.BYTE -> "byte[]"
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
BaseDataType.WORD -> "word[] (split)"
BaseDataType.UWORD -> "uword[] (split)"
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
}
BaseDataType.STRUCT_INSTANCE -> {
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
}
else -> base.name.lowercase()
}
fun sourceString(): String = when (base) {
BaseDataType.BOOL -> "bool"
BaseDataType.UBYTE -> "ubyte"
BaseDataType.BYTE -> "byte"
BaseDataType.UWORD -> "uword"
BaseDataType.WORD -> "word"
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "str"
BaseDataType.POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}"
subType!=null -> "^^${subType!!.scopedNameString}"
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
else -> "?????"
}
}
BaseDataType.STRUCT_INSTANCE -> {
when {
sub!=null -> sub.name.lowercase()
subType!=null -> subType!!.scopedNameString
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
else -> "?????"
}
}
BaseDataType.ARRAY_POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}["
subType!=null -> "^^${subType!!.scopedNameString}["
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
else -> "????? ["
}
}
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
BaseDataType.UWORD -> "@nosplit uword["
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
BaseDataType.UWORD -> "uword["
BaseDataType.WORD -> "word["
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
}
// is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
infix fun isAssignableTo(targetType: DataType) =
when(base) {
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
targetType.isPointer -> this.isUnsignedWord || this == targetType
else -> false
}
}
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
BaseDataType.ARRAY_POINTER -> false
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
// note: for pointer types, size() doesn't return the size of the pointer itself but the size of the thing it points to
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
memsizer.memorySize(sub)
} else if(subType!=null) {
subType!!.memsize(memsizer)
} else {
memsizer.memorySize(base)
}
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
val isUndefined = base == BaseDataType.UNDEFINED
val isByte = base.isByte
val isUnsignedByte = base == BaseDataType.UBYTE
val isSignedByte = base == BaseDataType.BYTE
val isByteOrBool = base.isByteOrBool
val isWord = base.isWord
val isUnsignedWord = base == BaseDataType.UWORD
val isSignedWord = base == BaseDataType.WORD
val isInteger = base.isInteger
val isWordOrByteOrBool = base.isWordOrByteOrBool
val isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isPointer = base.isPointer
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
val isPointerToWord = base.isPointer && sub?.isWord==true
val isStructInstance = base.isStructInstance
val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
}
enum class CpuRegister {
A,
@@ -406,7 +23,7 @@ enum class RegisterOrPair {
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
companion object {
val names by lazy { entries.map { it.toString()} }
val names: Set<String> = entries.map { it.toString() }.toSet()
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
@@ -440,12 +57,13 @@ enum class RegisterOrPair {
}
fun asScopedNameVirtualReg(type: DataType?): List<String> {
require(this in Cx16VirtualRegisters)
require(this in Cx16VirtualRegisters || this in CombinedLongRegisters)
val suffix = when(type?.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
BaseDataType.LONG -> "sl"
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
}
return listOf("cx16", name.lowercase()+suffix)
@@ -463,7 +81,7 @@ enum class Statusflag {
Pn; // don't use
companion object {
val names by lazy { entries.map { it.toString()} }
val names: Set<String> = entries.map { it.toString() }.toSet()
}
}
@@ -482,14 +100,14 @@ enum class BranchCondition {
VC
}
val Cx16VirtualRegisters = arrayOf(
val Cx16VirtualRegisters = setOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val CombinedLongRegisters = arrayOf(
val CombinedLongRegisters = setOf(
RegisterOrPair.R0R1,
RegisterOrPair.R2R3,
RegisterOrPair.R4R5,
@@ -500,7 +118,7 @@ val CombinedLongRegisters = arrayOf(
RegisterOrPair.R14R15
)
val CpuRegisters = arrayOf(
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
@@ -536,4 +154,4 @@ enum class ZeropageWish {
enum class SplitWish {
DONTCARE,
NOSPLIT
}
}
@@ -13,7 +13,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val FLOAT_MEM_SIZE: UInt
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
@@ -24,7 +24,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: List<String>
+21 -59
View File
@@ -28,26 +28,21 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
abstract val SCRATCH_PTR : UInt // temp storage for a pointer
// the variables allocated into Zeropage.
// Collections for zeropage allocation (single-threaded usage)
// name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<String, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
fun removeReservedFromFreePool() {
synchronized(this) {
for (reserved in options.zpReserved)
reserve(reserved)
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
}
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
}
fun retainAllowed() {
synchronized(this) {
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
@@ -65,7 +60,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
require(name.isEmpty() || !allocatedVariables.containsKey(name)) {"name can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
return Err(MemAllocationError("zero page usage has been disabled"))
@@ -95,19 +90,17 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
else -> throw MemAllocationError("weird dt")
}
synchronized(this) {
if(free.isNotEmpty()) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if(oneSeparateByteFree(candidate))
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
}
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
}
if(free.isNotEmpty()) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
if(oneSeparateByteFree(candidate))
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
}
return Ok(VarAllocation(makeAllocation(free.first(), 1, datatype, name), datatype,1))
}
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
}
}
@@ -131,43 +124,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
return address
}
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
private fun oneSeparateByteFree(address: UInt) =
address in free &&
(address == 0u || address-1u !in free) &&
address+1u !in free
private fun sequentialFree(address: UInt, size: Int): Boolean {
require(size>0)
return free.containsAll((address until address+size.toUInt()).toList())
}
}
// TODO: this class is not yet used
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
private var nextLocation: UInt = region.first
override fun allocate(
name: String,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
val size: Int =
when {
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isString -> numElements!!
datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!)
datatype.isFloat -> {
if (options.floats) {
options.compTarget.memorySize(DataType.FLOAT, null)
} else return Err(MemAllocationError("floating point option not enabled"))
}
else -> throw MemAllocationError("weird dt")
}
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
val result = Ok(VarAllocation(nextLocation, datatype, size))
nextLocation += size.toUInt()
result
} else
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
}
}
+5 -5
View File
@@ -1,10 +1,10 @@
package prog8.code.core
val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = arrayOf("and", "or", "xor", "not", "in")
val BitwiseOperators = arrayOf("&", "|", "^", "~")
val PrefixOperators = arrayOf("+", "-", "~", "not")
val CommutativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not commutative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
fun invertedComparisonOperator(operator: String) =
when (operator) {
+14 -11
View File
@@ -5,14 +5,17 @@ import prog8.code.source.SourceCode
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol}-${endCol}]"
init {
if(!file.startsWith('~') ||!file.endsWith('~'))
require(line>0 && startCol>=0 && endCol>=startCol) {
"Invalid position: $this"
}
/**
* Source code position.
*/
data class Position(
val file: String,
val line: Int, // line number (1-based)
val startCol: Int, // start column (1-based, tab-expanded)
val endCol: Int // end column (1-based, tab-expanded)
) {
override fun toString(): String {
return "[$file: line $line col ${startCol}-${endCol}]"
}
fun toClickableStr(): String {
@@ -21,8 +24,8 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
if(SourceCode.isLibraryResource(file))
return "$file:$line:$startCol:"
return try {
val path = Path(file).sanitize().toString()
"file://$path:$line:$startCol:"
val path = Path(file).sanitize().toUri().toString()
"$path:$line:$startCol:"
} catch(_: InvalidPathException) {
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
"file://$file:$line:$startCol:"
@@ -32,4 +35,4 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
companion object {
val DUMMY = Position("~dummy~", 0, 0, 0)
}
}
}
@@ -3,20 +3,16 @@ package prog8.code.source
import prog8.code.core.Position
import prog8.code.sanitize
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.io.path.Path
// Resource caching "filesystem".
// Note that it leaves the decision to load a resource or an actual disk file to the caller.
// Thread-safe: uses ConcurrentHashMap for all caches.
object ImportFileSystem {
fun clearCaches() {
cache.clear()
lineSpanCache.clear()
}
fun expandTilde(path: String): String = if (path.startsWith("~")) {
val userHome = System.getProperty("user.home")
userHome + path.drop(1)
@@ -28,45 +24,40 @@ object ImportFileSystem {
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
val normalized = path.sanitize()
val cached = cache[normalized.toString()]
if (cached != null)
return cached
val file = SourceCode.File(normalized, isLibrary)
cache[normalized.toString()] = file
return file
return cache.computeIfAbsent(normalized.toString().lowercase()) {
SourceCode.File(normalized, isLibrary)
}
}
fun getResource(name: String): SourceCode {
val cached = cache[name]
if (cached != null) return cached
val resource = SourceCode.Resource(name)
cache[name] = resource
return resource
return cache.computeIfAbsent(name.lowercase()) {
SourceCode.Resource(name)
}
}
fun retrieveSourceLine(position: Position): String {
if(SourceCode.isLibraryResource(position.file)) {
val cached = cache[SourceCode.withoutPrefix(position.file)]
val key = SourceCode.withoutPrefix(position.file)
val cached = cache[key.lowercase()]
?: runCatching { getResource(key) }.getOrNull()
if(cached != null)
return getLine(cached, position.line)
}
val cached = cache[position.file]
val cached = cache[position.file.lowercase()]
if(cached != null)
return getLine(cached, position.line)
val path = Path(position.file).sanitize()
val cached2 = cache[path.toString()]
val cached2 = cache[path.toString().lowercase()]
if(cached2 != null)
return getLine(cached2, position.line)
throw NoSuchElementException("cannot get source line $position, with path $path")
}
private fun getLine(code: SourceCode, lineIndex: Int): String {
var spans = lineSpanCache[code]
if(spans==null) {
val spans = lineSpanCache.computeIfAbsent(code) {
val lineSpans = Regex("^", RegexOption.MULTILINE).findAll(code.text).map { it.range.first }
val ends = lineSpans.drop(1) + code.text.length
spans = lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
lineSpanCache[code] = spans
lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
}
val span = spans[lineIndex - 1]
return code.text.substring(span.start, span.end).trim()
@@ -74,6 +65,6 @@ object ImportFileSystem {
private class LineSpan(val start: Int, val end: Int)
private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER)
private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>()
}
private val cache = ConcurrentHashMap<String, SourceCode>()
private val lineSpanCache = ConcurrentHashMap<SourceCode, Array<LineSpan>>()
}
+6 -8
View File
@@ -26,18 +26,17 @@ class C128Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
override val BSSHIGHRAM_START = 0u // no high ram
override val BSSHIGHRAM_END = 0u // no high ram
override val BSSGOLDENRAM_START = 0x1300u
override val BSSGOLDENRAM_END = 0x1bdfu // note: $1be0 - $1bff contains the 16 virtual registers
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -71,11 +70,10 @@ class C128Target: ICompilationTarget,
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu || address in 0xff00u..0xff04u
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
}
}
+1 -3
View File
@@ -29,7 +29,7 @@ class C64Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
@@ -41,7 +41,6 @@ class C64Target: ICompilationTarget,
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -87,7 +86,6 @@ class C64Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
@@ -145,10 +145,9 @@ class ConfigFileTarget(
// TODO floats are not yet supported here, just enter some values
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 8
override val FLOAT_MEM_SIZE = 8u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam // TODO this is not yet used
override fun getFloatAsmBytes(num: Number) = TODO("floats")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
@@ -168,6 +167,5 @@ class ConfigFileTarget(
zpFullsafe,
compilerOptions
)
// note: there's no golden ram yet
}
}
+1 -3
View File
@@ -26,7 +26,7 @@ class Cx16Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
@@ -37,7 +37,6 @@ class Cx16Target: ICompilationTarget,
override val BSSGOLDENRAM_END = 0x07ffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -87,7 +86,6 @@ class Cx16Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
}
+2 -2
View File
@@ -19,7 +19,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
val flt = num.toDouble()
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $flt")
if (flt == 0.0)
return zero
@@ -40,7 +40,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent > 255 -> throw InternalCompilerException("floating point overflow: $flt")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
+1 -3
View File
@@ -25,7 +25,7 @@ class PETTarget: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
@@ -36,7 +36,6 @@ class PETTarget: ICompilationTarget,
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -74,7 +73,6 @@ class PETTarget: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
+7 -5
View File
@@ -28,7 +28,7 @@ class VMTarget: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
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
@@ -38,7 +38,6 @@ class VMTarget: ICompilationTarget,
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
@@ -69,6 +68,10 @@ class VMTarget: ICompilationTarget,
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
launchEmulatorWithTrace(programNameWithPath, quiet, traceEnabled = false)
}
fun launchEmulatorWithTrace(programNameWithPath: Path, quiet: Boolean, traceEnabled: Boolean) {
if(!quiet)
println("\nStarting Virtual Machine...")
@@ -76,7 +79,7 @@ class VMTarget: ICompilationTarget,
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
vm.runProgram(withExt.readText(), quiet, traceEnabled)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
@@ -85,14 +88,13 @@ class VMTarget: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: String, quiet: Boolean)
fun runProgram(irSource: String, quiet: Boolean, traceEnabled: Boolean = false)
}
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
@@ -43,13 +43,13 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
))
// if(options.zeropage==ZeropageType.BASICSAFE) {
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
// }
// C128 doesn't support floating point calculations in Prog8, so we can use the FP zeropage locations
// for general variables in both BASICSAFE and FLOATSAFE modes.
// These addresses are normally used by the C128 BASIC floating point routines.
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@@ -63,7 +63,9 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
// C128 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// C128 has limited zeropage space in BASICSAFE/FLOATSAFE modes.
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
@@ -9,7 +9,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
override val SCRATCH_PTR = 0x9bu // temp storage for a pointer $9b+$9c
override val SCRATCH_PTR = 0x04u // temp storage for a pointer $04+$05
init {
@@ -57,7 +57,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(arrayOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
0x92, 0x96, 0x9c, 0x9e, 0x9f, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -72,7 +72,10 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
// C64 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// C64 has limited zeropage space compared to CX16. In BASICSAFE and FLOATSAFE modes,
// there isn't enough contiguous space for all 32 bytes of virtual register variants.
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
@@ -22,41 +22,47 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
synchronized(this) {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.FLOATSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xd4u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
// Initialize free list - no synchronization needed during construction
// (object reference doesn't escape until fully constructed)
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.FLOATSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xd4u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// CX16 DESIGN: Virtual registers are ALWAYS allocated in zeropage for all ZeropageType modes.
// The Commander X16 has ample zeropage space (0x22-0x7F in BASICSAFE, up to 0x22-0xFF in FULL),
// so virtual registers can always fit. This ensures consistent behavior across all modes.
// Virtual registers occupy addresses 0x02-0x21 (32 bytes for r0-r15 variants).
//
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped*
// elsewhere to fixed addresses. However, to be able for the compiler to "see" them as zeropage
// variables, we have to register them here as well. This is important because the compiler
// sometimes treats ZP variables more efficiently (for example if it's a pointer).
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
@@ -46,7 +46,9 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
// PET DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// PET has very limited zeropage space in BASICSAFE/FLOATSAFE modes (only 0xB1-0xBA).
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
@@ -0,0 +1,67 @@
package prog8tests.helpers
import prog8.code.core.*
/**
* Dummy IMemSizer implementation for testing.
* Returns standard memory sizes for all data types.
*/
object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray)
return 2 * numElements!!
else if (dt.isArray) {
require(numElements != null)
return when (dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT -> numElements * 5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
/**
* Dummy IStringEncoding implementation for testing.
* Returns empty list/string for all operations.
*/
object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
/**
* ASCII IStringEncoding implementation for testing.
* Encodes/decodes using simple ASCII mapping.
*/
object AsciiStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> =
str.map { it.code.toUByte() }
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String =
bytes.joinToString("") { it.toInt().toChar().toString() }
}
@@ -3,7 +3,17 @@ package prog8tests.helpers
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
/**
* Test implementation of IErrorReporter that collects errors/warnings/infos in memory.
*
* @param throwExceptionAtReportIfErrors If true, report() will throw an exception when errors exist
* @param keepMessagesAfterReporting If false, clear() is called after report()
*/
class ErrorReporterForTests(
private val throwExceptionAtReportIfErrors: Boolean = true,
private val keepMessagesAfterReporting: Boolean = false
) : IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
@@ -11,19 +21,19 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in errors)
if (text !in errors)
errors.add(text)
}
override fun warn(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
if (text !in warnings)
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
if (text !in infos)
infos.add(text)
}
@@ -31,17 +41,22 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position): Boolean =
!errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) {
// prints nothing in tests
}
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
if (throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
if (!keepMessagesAfterReporting) {
clear()
}
}
@@ -0,0 +1,109 @@
package prog8tests.codecore
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.core.escape
import prog8.code.core.toHex
import prog8.code.core.unescape
/**
* Unit tests for conversion functions in codeCore.
* Focus on toHex boundaries and escape/unescape roundtrips.
*/
class TestConversions: FunSpec({
// ============================================================================
// toHex() Tests
// ============================================================================
test("toHex for 0-15 (decimal output)") {
0.toHex() shouldBe "0"
10.toHex() shouldBe "10"
15.toHex() shouldBe "15"
}
test("toHex for 16-255 (hex with $)") {
16.toHex() shouldBe "\$10"
255.toHex() shouldBe "\$ff"
}
test("toHex for 256-65535 (4 digit hex)") {
256.toHex() shouldBe "\$0100"
65535.toHex() shouldBe "\$ffff"
}
test("toHex for larger numbers (8 digit hex)") {
65536.toHex() shouldBe "\$00010000"
0x12345678.toHex() shouldBe "\$12345678"
}
test("toHex for negative numbers") {
(-1).toHex() shouldBe "-1"
(-255).toHex() shouldBe "-\$ff"
Int.MIN_VALUE.toHex() shouldBe "\$80000000"
}
test("UInt.toHex") {
0u.toHex() shouldBe "0"
15u.toHex() shouldBe "15"
255u.toHex() shouldBe "\$ff"
65535u.toHex() shouldBe "\$ffff"
}
// ============================================================================
// escape() Tests
// ============================================================================
test("escape for plain string") {
"hello".escape() shouldBe "hello"
}
test("escape for tab and newline") {
"hello\tworld".escape() shouldBe "hello\\tworld"
"hello\nworld".escape() shouldBe "hello\\nworld"
}
test("escape for double quote") {
"hello\"world".escape() shouldBe "hello\\\"world"
}
test("escape for unicode outside passthrough range") {
"€".escape() shouldBe "\\u20ac"
}
test("escape for passthrough characters (\\u8000-\\u80ff)") {
"\u8000".escape() shouldBe "\\x00"
"\u80ff".escape() shouldBe "\\xff"
}
// ============================================================================
// unescape() Tests
// ============================================================================
test("unescape for plain string") {
"hello".unescape() shouldBe "hello"
}
test("unescape for tab and newline") {
"hello\\tworld".unescape() shouldBe "hello\tworld"
"hello\\nworld".unescape() shouldBe "hello\nworld"
}
test("unescape for double quote") {
"hello\\\"world".unescape() shouldBe "hello\"world"
}
test("unescape for unicode") {
"\\u0041".unescape() shouldBe "A"
"\\u20ac".unescape() shouldBe "€"
}
// ============================================================================
// Roundtrip Tests
// ============================================================================
test("escape then unescape roundtrip") {
val original = "hello\t\nworld\"test"
original.escape().unescape() shouldBe original
}
})
@@ -0,0 +1,296 @@
package prog8tests.codecore
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
/**
* Unit tests for DataType and BaseDataType classes in codeCore.
* Focus on type predicates, relationships, and key operations.
*/
class TestDataType: FunSpec({
val dummyMemSizer = object : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray) return 2 * numElements!!
if (dt.isArray || dt.isSplitWordArray) {
require(numElements != null)
return when (dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT -> numElements * 5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
// ============================================================================
// BaseDataType Extension Property Tests
// ============================================================================
test("isByte extension") {
BaseDataType.UBYTE.isByte.shouldBeTrue()
BaseDataType.BYTE.isByte.shouldBeTrue()
BaseDataType.UWORD.isByte.shouldBeFalse()
BaseDataType.WORD.isByte.shouldBeFalse()
}
test("isByteOrBool extension") {
BaseDataType.UBYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BOOL.isByteOrBool.shouldBeTrue()
BaseDataType.UWORD.isByteOrBool.shouldBeFalse()
}
test("isWord extension") {
BaseDataType.UWORD.isWord.shouldBeTrue()
BaseDataType.WORD.isWord.shouldBeTrue()
BaseDataType.UBYTE.isWord.shouldBeFalse()
}
test("isInteger extension") {
BaseDataType.UBYTE.isInteger.shouldBeTrue()
BaseDataType.WORD.isInteger.shouldBeTrue()
BaseDataType.LONG.isInteger.shouldBeTrue()
BaseDataType.FLOAT.isInteger.shouldBeFalse()
}
test("isNumeric extension") {
BaseDataType.UBYTE.isNumeric.shouldBeTrue()
BaseDataType.FLOAT.isNumeric.shouldBeTrue()
BaseDataType.BOOL.isNumeric.shouldBeFalse()
}
test("isSigned extension") {
BaseDataType.BYTE.isSigned.shouldBeTrue()
BaseDataType.WORD.isSigned.shouldBeTrue()
BaseDataType.LONG.isSigned.shouldBeTrue()
BaseDataType.UBYTE.isSigned.shouldBeFalse()
}
test("isArray extension") {
BaseDataType.ARRAY.isArray.shouldBeTrue()
BaseDataType.ARRAY_SPLITW.isArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isArray.shouldBeTrue()
BaseDataType.UBYTE.isArray.shouldBeFalse()
}
test("isPointer extension") {
BaseDataType.POINTER.isPointer.shouldBeTrue()
BaseDataType.UBYTE.isPointer.shouldBeFalse()
}
test("isSplitWordArray extension") {
BaseDataType.ARRAY_SPLITW.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY.isSplitWordArray.shouldBeFalse()
}
// ============================================================================
// DataType Companion Object Tests
// ============================================================================
test("DataType.forDt creates simple types") {
DataType.forDt(BaseDataType.UBYTE) shouldBe DataType.UBYTE
DataType.forDt(BaseDataType.WORD) shouldBe DataType.WORD
DataType.forDt(BaseDataType.LONG) shouldBe DataType.LONG
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
}
test("DataType.forDt throws for struct instance") {
shouldThrow<NotImplementedError> {
DataType.forDt(BaseDataType.STRUCT_INSTANCE)
}
}
test("DataType.arrayFor creates array types") {
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE)
ubyteArray.base shouldBe BaseDataType.ARRAY
ubyteArray.sub shouldBe BaseDataType.UBYTE
val wordArraySplit = DataType.arrayFor(BaseDataType.WORD)
wordArraySplit.base shouldBe BaseDataType.ARRAY_SPLITW
wordArraySplit.sub shouldBe BaseDataType.WORD
}
test("DataType.pointer creates pointer types") {
val ptrByte = DataType.pointer(BaseDataType.UBYTE)
ptrByte.base shouldBe BaseDataType.POINTER
ptrByte.sub shouldBe BaseDataType.UBYTE
}
// ============================================================================
// DataType Property Tests
// ============================================================================
test("DataType.isBasic property") {
DataType.UBYTE.isBasic.shouldBeTrue()
DataType.WORD.isBasic.shouldBeTrue()
DataType.STR.isBasic.shouldBeFalse()
DataType.arrayFor(BaseDataType.UBYTE).isBasic.shouldBeFalse()
}
test("DataType.isByte property") {
DataType.UBYTE.isByte.shouldBeTrue()
DataType.BYTE.isByte.shouldBeTrue()
DataType.WORD.isByte.shouldBeFalse()
}
test("DataType.isNumeric property") {
DataType.UBYTE.isNumeric.shouldBeTrue()
DataType.FLOAT.isNumeric.shouldBeTrue()
DataType.BOOL.isNumeric.shouldBeFalse()
DataType.STR.isNumeric.shouldBeFalse()
}
test("DataType.isArray property") {
DataType.arrayFor(BaseDataType.UBYTE).isArray.shouldBeTrue()
DataType.UBYTE.isArray.shouldBeFalse()
}
test("DataType.isPointer property") {
DataType.pointer(BaseDataType.UBYTE).isPointer.shouldBeTrue()
DataType.UBYTE.isPointer.shouldBeFalse()
}
test("DataType.isString property") {
DataType.STR.isString.shouldBeTrue()
DataType.UBYTE.isString.shouldBeFalse()
}
test("DataType.isPassByRef property") {
DataType.STR.isPassByRef.shouldBeTrue()
DataType.arrayFor(BaseDataType.UBYTE).isPassByRef.shouldBeTrue()
DataType.UBYTE.isPassByRef.shouldBeFalse()
DataType.pointer(BaseDataType.UBYTE).isPassByRef.shouldBeFalse()
}
// ============================================================================
// DataType Method Tests
// ============================================================================
test("DataType.elementType returns element type for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).elementType() shouldBe DataType.UBYTE
DataType.arrayFor(BaseDataType.WORD).elementType() shouldBe DataType.WORD
DataType.STR.elementType() shouldBe DataType.UBYTE
}
test("DataType.elementType throws for non-arrays") {
shouldThrow<IllegalArgumentException> {
DataType.UBYTE.elementType()
}
}
test("DataType.dereference for pointers") {
DataType.pointer(BaseDataType.UBYTE).dereference() shouldBe DataType.UBYTE
DataType.pointer(BaseDataType.WORD).dereference() shouldBe DataType.WORD
DataType.UWORD.dereference() shouldBe DataType.UBYTE
}
test("DataType.size with memsizer") {
DataType.UBYTE.size(dummyMemSizer) shouldBe 1
DataType.WORD.size(dummyMemSizer) shouldBe 2
DataType.LONG.size(dummyMemSizer) shouldBe 4
DataType.FLOAT.size(dummyMemSizer) shouldBe 5
}
// ============================================================================
// DataType Comparison Tests
// ============================================================================
test("DataType equals for same types") {
DataType.UBYTE shouldBe DataType.UBYTE
DataType.WORD shouldBe DataType.WORD
DataType.STR shouldBe DataType.STR
}
test("DataType equals for different types") {
DataType.UBYTE shouldNotBe DataType.BYTE
DataType.UBYTE shouldNotBe DataType.WORD
}
test("BaseDataType.largerSizeThan") {
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
}
test("BaseDataType.equalsSize") {
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
}
// ============================================================================
// DataType.isAssignableTo Tests
// ============================================================================
test("bool assignability") {
DataType.BOOL.isAssignableTo(DataType.BOOL).shouldBeTrue()
DataType.BOOL.isAssignableTo(DataType.UBYTE).shouldBeFalse()
}
test("ubyte assignability") {
DataType.UBYTE.isAssignableTo(DataType.UBYTE).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.FLOAT).shouldBeTrue()
}
test("byte assignability") {
DataType.BYTE.isAssignableTo(DataType.BYTE).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.WORD).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
}
test("uword assignability") {
DataType.UWORD.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.FLOAT).shouldBeTrue()
}
test("string assignability") {
DataType.STR.isAssignableTo(DataType.STR).shouldBeTrue()
DataType.STR.isAssignableTo(DataType.UWORD).shouldBeTrue()
}
test("pointer assignability") {
val ptr = DataType.pointer(BaseDataType.UBYTE)
ptr.isAssignableTo(DataType.UWORD).shouldBeTrue()
ptr.isAssignableTo(DataType.LONG).shouldBeTrue()
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)).shouldBeTrue()
}
// ============================================================================
// DataType.toString Tests
// ============================================================================
test("DataType.toString for basic types") {
DataType.UBYTE.toString() shouldBe "ubyte"
DataType.WORD.toString() shouldBe "word"
DataType.LONG.toString() shouldBe "long"
DataType.FLOAT.toString() shouldBe "float"
}
test("DataType.toString for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).toString() shouldBe "ubyte[]"
DataType.arrayFor(BaseDataType.WORD).toString() shouldBe "word[] (split)"
}
})
@@ -0,0 +1,721 @@
package prog8tests.codecore
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
import prog8.code.target.C128Target
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.code.target.zp.C128Zeropage
import prog8.code.target.zp.C64Zeropage
import prog8.code.target.zp.CX16Zeropage
import prog8.code.target.zp.PETZeropage
/**
* Comprehensive tests for Zeropage free list configurations across all targets.
*
* Tests verify that for each target and ZeropageType combination:
* - The exact list of free locations is correct
* - Scratch locations are properly reserved
* - Virtual registers are allocated when appropriate
*/
class TestZeropageFreeList: FunSpec({
// ============================================================================
// Helper Functions
// ============================================================================
fun createOptions(target: ICompilationTarget, zpType: ZeropageType, floats: Boolean = false): CompilationOptions {
return CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(zpType)
.floats(floats)
.compilerVersion("test")
.build()
}
fun verifyScratchLocationsReserved(zp: Zeropage) {
// Verify scratch locations are not in free list
zp.free.contains(zp.SCRATCH_B1) shouldBe false
zp.free.contains(zp.SCRATCH_REG) shouldBe false
zp.free.contains(zp.SCRATCH_W1) shouldBe false
zp.free.contains(zp.SCRATCH_W1 + 1u) shouldBe false
zp.free.contains(zp.SCRATCH_W2) shouldBe false
zp.free.contains(zp.SCRATCH_W2 + 1u) shouldBe false
zp.free.contains(zp.SCRATCH_PTR) shouldBe false
zp.free.contains(zp.SCRATCH_PTR + 1u) shouldBe false
}
fun verifyVirtualRegistersAllocated(zp: Zeropage, expected: Boolean) {
if (expected) {
// Check that virtual registers are allocated
for (reg in 0..15) {
zp.allocatedVariables shouldContainKey "cx16.r${reg}"
}
}
}
// ============================================================================
// C64 Zeropage Tests
// ============================================================================
context("C64Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
val zp = C64Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have specific free locations") {
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val zp = C64Zeropage(options)
// BASICSAFE includes specific BASIC-safe locations
zp.free.isNotEmpty().shouldBeTrue()
zp.free.size shouldBeGreaterThan 0
// Verify scratch locations are reserved
verifyScratchLocationsReserved(zp)
// Virtual registers should be allocated for BASICSAFE
verifyVirtualRegistersAllocated(zp, expected = false) // C64 doesn't allocate virtual regs for BASICSAFE
}
test("KERNALSAFE should have larger free list than BASICSAFE") {
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val zp = C64Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// KERNALSAFE has more locations than BASICSAFE
val basicsafeOptions = createOptions(C64Target(), ZeropageType.BASICSAFE)
val basicsafeZp = C64Zeropage(basicsafeOptions)
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true) // KERNALSAFE gets virtual registers
}
test("FLOATSAFE should exclude float-used locations") {
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = C64Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// FLOATSAFE should have fewer locations than KERNALSAFE due to float exclusions
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
// Some float-related locations should not be free (exact set depends on implementation)
// Just verify the size difference is significant
(kernalsafeZp.free.size - zp.free.size) shouldBeGreaterThan 10
verifyScratchLocationsReserved(zp)
}
test("FULL should have maximum free locations minus IRQ") {
val options = createOptions(C64Target(), ZeropageType.FULL)
val zp = C64Zeropage(options)
// FULL should have the most free locations
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
// IRQ locations should not be free
zp.free.contains(0xa0u) shouldBe false
zp.free.contains(0xa1u) shouldBe false
zp.free.contains(0xa2u) shouldBe false
zp.free.contains(0x91u) shouldBe false
zp.free.contains(0xc0u) shouldBe false
zp.free.contains(0xc5u) shouldBe false
zp.free.contains(0xcbu) shouldBe false
zp.free.contains(0xf5u) shouldBe false
zp.free.contains(0xf6u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// CX16 Zeropage Tests
// ============================================================================
context("CX16Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
val zp = CX16Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have 0x22-0x7F") {
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 should be allocated, not free
for (addr in 0x02u..0x21u) {
zp.free shouldNotBe addr
}
// Free should be 0x22-0x7F minus scratch locations
for (addr in 0x22u..0x7Fu) {
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
zp.free.contains(addr) shouldBe true
}
}
verifyScratchLocationsReserved(zp)
}
test("KERNALSAFE should have 0x22-0x7F and 0xA9-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val zp = CX16Zeropage(options)
val basicsafeOptions = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val basicsafeZp = CX16Zeropage(basicsafeOptions)
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
// Should include high memory range
zp.free.contains(0xa9u) shouldBe true
zp.free.contains(0xffu) shouldBe true
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should have 0x22-0x7F and 0xD4-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = CX16Zeropage(options)
// Should include 0xD4-0xFF range
zp.free.contains(0xd4u) shouldBe true
zp.free.contains(0xffu) shouldBe true
// FLOATSAFE has different high range than KERNALSAFE (0xD4-0xFF vs 0xA9-0xFF)
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
// FLOATSAFE should have fewer locations than KERNALSAFE (smaller high range)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
verifyScratchLocationsReserved(zp)
}
test("FULL should have 0x22-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FULL)
val zp = CX16Zeropage(options)
// FULL should have the most free locations
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
// Should include all locations from 0x22 to 0xFF (minus scratch)
for (addr in 0x80u..0xA8u) {
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
zp.free.contains(addr) shouldBe true
}
}
verifyScratchLocationsReserved(zp)
}
}
// ============================================================================
// PET Zeropage Tests
// ============================================================================
context("PETZeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
val zp = PETZeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have minimal locations") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
zp.free.isNotEmpty().shouldBeTrue()
// After scratch removal, some locations in 0xb1-0xba range should still be free
// Just verify the free list is small but non-empty
zp.free.size shouldBeGreaterThan 0
zp.free.size shouldBeLessThan 20
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should be same as BASICSAFE") {
val floatOptions = createOptions(PETTarget(), ZeropageType.FLOATSAFE, floats = true)
val floatZp = PETZeropage(floatOptions)
val basicOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val basicZp = PETZeropage(basicOptions)
// FLOATSAFE and BASICSAFE should have same free locations for PET
floatZp.free.size shouldBe basicZp.free.size
}
test("KERNALSAFE should exclude IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
val zp = PETZeropage(options)
// IRQ locations should not be free
zp.free.contains(0x8du) shouldBe false
zp.free.contains(0x8eu) shouldBe false
zp.free.contains(0x8fu) shouldBe false
zp.free.contains(0x97u) shouldBe false
zp.free.contains(0x98u) shouldBe false
zp.free.contains(0x99u) shouldBe false
zp.free.contains(0x9au) shouldBe false
zp.free.contains(0x9bu) shouldBe false
zp.free.contains(0x9eu) shouldBe false
zp.free.contains(0xa7u) shouldBe false
zp.free.contains(0xa8u) shouldBe false
zp.free.contains(0xa9u) shouldBe false
zp.free.contains(0xaau) shouldBe false
zp.free.contains(0xaaau) shouldBe false
verifyScratchLocationsReserved(zp)
}
test("FULL should have all locations minus IRQ") {
val options = createOptions(PETTarget(), ZeropageType.FULL)
val zp = PETZeropage(options)
// FULL should have most locations
zp.free.size shouldBeGreaterThan 100
// IRQ locations should not be free
zp.free.contains(0x8du) shouldBe false
zp.free.contains(0x97u) shouldBe false
zp.free.contains(0xa7u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// C128 Zeropage Tests
// ============================================================================
context("C128Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
val zp = C128Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have specific safe locations") {
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val zp = C128Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// Should include some specific BASIC-safe locations (excluding scratch)
// 0x0b is SCRATCH_PTR, 0x74 is SCRATCH_B1 for C128, so they're not free
// Check for other locations that should be free
zp.free.contains(0x0du) shouldBe true
zp.free.contains(0x1bu) shouldBe true // In BASICSAFE range
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should be same as BASICSAFE for C128") {
val floatOptions = createOptions(C128Target(), ZeropageType.FLOATSAFE, floats = true)
val floatZp = C128Zeropage(floatOptions)
val basicOptions = createOptions(C128Target(), ZeropageType.BASICSAFE)
val basicZp = C128Zeropage(basicOptions)
// FLOATSAFE and BASICSAFE should have same free locations for C128
floatZp.free.size shouldBe basicZp.free.size
}
test("KERNALSAFE should have BASIC variables area") {
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
val zp = C128Zeropage(options)
// Should include 0x0A-0x8F (BASIC variables area) minus scratch/virtual reg locations
// 0x0a is where virtual registers start for C128, so not free
// 0x8fu should be free
zp.free.contains(0x8fu) shouldBe true
// Should include additional specific locations
zp.free.contains(0x92u) shouldBe true
// 0xb6u is SCRATCH_W1 for C128 - won't be free
zp.free.contains(0x96u) shouldBe true // Alternative location
verifyScratchLocationsReserved(zp)
}
test("FULL should have maximum locations minus IRQ") {
val options = createOptions(C128Target(), ZeropageType.FULL)
val zp = C128Zeropage(options)
// FULL should have many locations (0x0A-0xFF minus IRQ and scratch)
// 0x0a is where virtual registers start for C128, so not free
zp.free.size shouldBeGreaterThan 150
// IRQ locations should not be free
zp.free.contains(0x90u) shouldBe false
zp.free.contains(0x91u) shouldBe false
zp.free.contains(0xa0u) shouldBe false
zp.free.contains(0xa1u) shouldBe false
zp.free.contains(0xa2u) shouldBe false
zp.free.contains(0xc0u) shouldBe false
zp.free.contains(0xccu) shouldBe false
zp.free.contains(0xcdu) shouldBe false
zp.free.contains(0xd0u) shouldBe false
zp.free.contains(0xd1u) shouldBe false
zp.free.contains(0xd2u) shouldBe false
zp.free.contains(0xd3u) shouldBe false
zp.free.contains(0xd4u) shouldBe false
zp.free.contains(0xd5u) shouldBe false
zp.free.contains(0xf7u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// Cross-Target Comparison Tests
// ============================================================================
context("Cross-Target Comparisons") {
test("all targets should have empty free list for DONTUSE") {
// C64
val c64Options = createOptions(C64Target(), ZeropageType.DONTUSE)
C64Zeropage(c64Options).free.shouldBeEmpty()
// CX16
val cx16Options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
CX16Zeropage(cx16Options).free.shouldBeEmpty()
// PET
val petOptions = createOptions(PETTarget(), ZeropageType.DONTUSE)
PETZeropage(petOptions).free.shouldBeEmpty()
// C128
val c128Options = createOptions(C128Target(), ZeropageType.DONTUSE)
C128Zeropage(c128Options).free.shouldBeEmpty()
}
test("scratch locations differ between targets") {
val c64Options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val c64Zp = C64Zeropage(c64Options)
val cx16Options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val cx16Zp = CX16Zeropage(cx16Options)
val petOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val petZp = PETZeropage(petOptions)
val c128Options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val c128Zp = C128Zeropage(c128Options)
// Each target has different scratch locations
c64Zp.SCRATCH_B1 shouldBe 0x02u
cx16Zp.SCRATCH_B1 shouldBe 0x7au
petZp.SCRATCH_B1 shouldBe 0xb3u
c128Zp.SCRATCH_B1 shouldBe 0x74u
}
}
// ============================================================================
// Exhaustive Zeropage Address Tests - All 256 locations
// ============================================================================
context("Exhaustive C64 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
val zp = C64Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - exact free address set") {
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val zp = C64Zeropage(options)
// BASICSAFE: specific BASIC-safe locations plus virtual registers at 0x04-0x23
// Scratch locations are removed from free list
// Just verify key properties
zp.free.size shouldBeGreaterThan 10
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Some known free locations
zp.free.contains(0x06u) shouldBe true
zp.free.contains(0x92u) shouldBe true
}
test("KERNALSAFE - exact free address set") {
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val zp = C64Zeropage(options)
// KERNALSAFE: larger set including more locations, plus virtual registers at 0x04-0x23
// Scratch locations (0x02,0x03,0x04,0x05,0xFB,0xFC,0xFD,0xFE) are removed
// Virtual registers 0x04-0x23 are allocated (not free)
// Just verify key properties since exact set is complex
zp.free.size shouldBeGreaterThan 50
// IRQ locations should NOT be free
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
test("FLOATSAFE - excludes float-used locations") {
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = C64Zeropage(options)
// FLOATSAFE is KERNALSAFE minus float-used locations
zp.free.size shouldBeGreaterThan 20
// FLOATSAFE should have fewer locations than KERNALSAFE
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
}
test("FULL - maximum free locations minus IRQ") {
val options = createOptions(C64Target(), ZeropageType.FULL)
val zp = C64Zeropage(options)
// FULL should have the most locations
zp.free.size shouldBeGreaterThan 100
// IRQ locations should NOT be free
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
context("Exhaustive CX16 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
val zp = CX16Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - 0x22-0x7F minus scratch and virtual registers") {
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// BASICSAFE: 0x22-0x7F minus scratch
for (addr in 0x22u..0x7Fu) {
val isScratch = addr in setOf(0x7Au, 0x7Bu, 0x7Cu, 0x7Du, 0x7Eu, 0x7Fu, 0x22u, 0x23u)
zp.free.contains(addr) shouldBe !isScratch
}
// Above 0x7F should not be free for BASICSAFE
for (addr in 0x80u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("KERNALSAFE - 0x22-0x7F and 0xA9-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// Should include 0xA9-0xFF range
zp.free.contains(0xA9u) shouldBe true
zp.free.contains(0xFFu) shouldBe true
}
test("FLOATSAFE - 0x22-0x7F and 0xD4-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = CX16Zeropage(options)
// Should include 0xD4-0xFF range
zp.free.contains(0xD4u) shouldBe true
zp.free.contains(0xFFu) shouldBe true
// Should NOT include 0xA9-0xD3 range that KERNALSAFE has
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
}
test("FULL - 0x22-0xFF minus scratch") {
val options = createOptions(Cx16Target(), ZeropageType.FULL)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// FULL should have most locations from 0x22-0xFF
zp.free.size shouldBeGreaterThan 150
}
}
context("Exhaustive PET Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
val zp = PETZeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
zp.free.size shouldBeGreaterThan 0
zp.free.size shouldBeLessThan 15
}
test("KERNALSAFE - excludes IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
val zp = PETZeropage(options)
// IRQ locations should NOT be free
setOf(0x8Du, 0x8Eu, 0x8Fu, 0x97u, 0x98u, 0x99u, 0x9Au, 0x9Bu, 0x9Eu,
0xA7u, 0xA8u, 0xA9u, 0xAAu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
test("FULL - all minus IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.FULL)
val zp = PETZeropage(options)
// FULL should have most locations
zp.free.size shouldBeGreaterThan 150
// IRQ locations should NOT be free
setOf(0x8Du, 0x97u, 0xA7u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
context("Exhaustive C128 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
val zp = C128Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - specific safe locations") {
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val zp = C128Zeropage(options)
// BASICSAFE has specific locations
zp.free.size shouldBeGreaterThan 50
// Note: Virtual registers are NOT allocated for BASICSAFE on C128
// (only for FULL and KERNALSAFE modes)
}
test("KERNALSAFE - BASIC variables area 0x0A-0x8F") {
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
val zp = C128Zeropage(options)
// Virtual registers at 0x0A-0x29 are allocated (not free)
for (addr in 0x0Au..0x29u) {
zp.free.contains(addr) shouldBe false
}
// Should include some locations in BASIC variables area
zp.free.contains(0x8Fu) shouldBe true
}
test("FULL - 0x0A-0xFF minus IRQ") {
val options = createOptions(C128Target(), ZeropageType.FULL)
val zp = C128Zeropage(options)
// FULL should have many locations
zp.free.size shouldBeGreaterThan 150
// IRQ locations should NOT be free
setOf(0x90u, 0x91u, 0xA0u, 0xA1u, 0xA2u, 0xC0u, 0xCCu, 0xCDu,
0xD0u, 0xD1u, 0xD2u, 0xD3u, 0xD4u, 0xD5u, 0xF7u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
})
// Helper infix function for readability
infix fun Int.shouldBeLessThan(value: Int) {
if (this >= value) {
throw AssertionError("Expected $this to be less than $value")
}
}
+4 -34
View File
@@ -5,39 +5,9 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
}
sourceSets {
main {
java {
setSrcDirs(listOf("$projectDir/src"))
}
resources {
setSrcDirs(listOf("$projectDir/res"))
}
}
test {
java {
setSrcDirs(listOf("$projectDir/test"))
}
}
}
tasks.test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn("cleanTest")
// Show test results.
testLogging {
events("skipped", "failed")
}
testImplementation(testFixtures(project(":codeCore")))
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
+3 -3
View File
@@ -13,7 +13,7 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
</module>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -17,6 +17,10 @@ internal class AssemblyProgram(
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
File diff suppressed because it is too large. Load diff
@@ -737,18 +737,18 @@ $loopLabel sty $indexVar
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
when {
iterableDt.isByteArray -> {
// loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $range ${stmt.position}")
}
2 -> {
if(range.last==255 || range.last==254) {
@@ -805,10 +805,9 @@ $loopLabel""")
}
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
// loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
}
else -> {
// word, step >= 2 or <= -2
@@ -826,7 +825,6 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
clc
@@ -843,11 +841,54 @@ $loopLabel""")
iterableDt.isLongArray -> {
// loop over long range via loopvar, step >= 2 or <= -2
when(range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
}
else -> {
TODO("for long range. Report this error please, and use word loops in the meantime ${stmt.position}")
// long, step >= 2 or <= -2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val from = range.first.toLongHex()
val to = range.last.toLongHex()
val step = range.step.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel
+ lda $varname
clc
adc #$${step.substring(6, 8)}
sta $varname
lda $varname+1
adc #$${step.substring(4, 6)}
sta $varname+1
lda $varname+2
adc #$${step.substring(2, 4)}
sta $varname+2
lda $varname+3
adc #$${step.take(2)}
sta $varname+3""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
@@ -987,12 +1028,106 @@ $loopLabel""")
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
require(range.step==1)
TODO("for long range step +1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
bne $loopLabel
inc $varname+2
bne $loopLabel
inc $varname+3""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
require(range.step==-1)
TODO("for long range step -1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++++
lda $varname+1
bne +++
lda $varname+2
bne ++
lda $varname+3
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel""")
}
asmgen.out("""
+ lda $varname
bne +++
lda $varname+1
bne ++
lda $varname+2
bne +
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
@@ -203,8 +203,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
is PtFunctionCall if arg.builtin -> {
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
usesOtherRegistersWhileEvaluating(arg.args[0])
else if (arg.name == "mkword" || arg.name == "mklong2")
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
@@ -268,8 +268,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val reg = parameter.register
if(reg!=null) {
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
val varName = "cx16.${reg.name.lowercase()}"
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val varName = reg.asScopedNameVirtualReg(value.type).joinToString(".")
asmgen.assignExpressionToVariable(value, varName, parameter.type)
} else {
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
@@ -333,7 +333,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, value.position)
} else {
val scope = value.definingISub()
val target: AsmAssignTarget =
@@ -344,7 +344,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -352,7 +352,7 @@ internal class ProgramAndVarsGen(
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -391,20 +391,23 @@ internal class ProgramAndVarsGen(
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
if(field.first.isFloat) {
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
val numValue = (value as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float field")
"["+compTarget.getFloatAsmBytes(numValue)+"]"
} else {
when {
value.number!=null -> {
when (value) {
is StArrayElement.Number -> {
if(field.first.isPointer)
"$"+value.number!!.toInt().toString(16)
"$"+value.value.toInt().toString(16)
else if(field.first.isInteger)
value.number!!.toInt().toString()
value.value.toInt().toString()
else
value.number.toString()
value.value.toString()
}
value.addressOfSymbol!=null -> value.addressOfSymbol!!
value.boolean!=null -> if(value.boolean==true) "1" else "0"
else -> throw AssemblyError("weird struct initial value $value")
is StArrayElement.AddressOf -> value.symbol
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
}
}
}
@@ -502,7 +505,7 @@ internal class ProgramAndVarsGen(
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -739,7 +742,7 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
}
alignedStrings.sortedBy { it.align }.forEach {
outputStringvar(
@@ -748,22 +751,22 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
}
notAlignedOther.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
}
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
}
}
}
@@ -914,7 +917,9 @@ internal class ProgramAndVarsGen(
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
compTarget.getFloatAsmBytes(it.number!!)
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float array")
compTarget.getFloatAsmBytes(numValue)
}
asmgen.out(varname)
for (f in array.zip(floatFills))
@@ -925,11 +930,7 @@ internal class ProgramAndVarsGen(
}
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
return List(numElts) { StArrayElement.Number(0.0) }
}
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
@@ -937,10 +938,17 @@ internal class ProgramAndVarsGen(
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.sortedBy { it.name }.forEach {
val nameWithoutColons = it.name.replace("::", "_")
if(it.dt.isFloat)
asmgen.out(" ${it.name} = ${it.value}")
else
asmgen.out(" ${it.name} = ${it.value.toHex()}")
asmgen.out(" ${nameWithoutColons} = ${it.value}")
else {
if(it.value!=null)
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
else if(it.memorySlab!=null)
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
else
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
}
}
}
@@ -970,41 +978,35 @@ internal class ProgramAndVarsGen(
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when {
dt.isBoolArray ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
if(it.boolean!=null)
if(it.boolean==true) "1" else "0"
else {
val number = it.number!!
if(number==0.0) "0" else "1"
when(it) {
is StArrayElement.BoolValue -> if(it.value) "1" else "0"
is StArrayElement.Number -> if(it.value==0.0) "0" else "1"
else -> throw AssemblyError("unexpected array element type for bool array")
}
}
dt.isUnsignedByteArray ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for ubyte array")
"$"+numValue.toInt().toString(16).padStart(2, '0')
}
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
else if(it.addressOfSymbol!=null) {
val addrOfSymbol = it.addressOfSymbol!!
val symbol = symboltable.lookup(addrOfSymbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(addrOfSymbol+"_lsb") // the _lsb split array comes first in memory
else
asmgen.asmSymbolName(addrOfSymbol)
}
else if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
when(it) {
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
}
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
is StArrayElement.StructInstance -> {
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
asmgen.asmSymbolName("$prefix.${it.name}")
}
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
}
}
else -> throw AssemblyError("invalid dt")
@@ -1014,41 +1016,66 @@ internal class ProgramAndVarsGen(
private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when {
// byte array can never contain pointer-to types, so treat values as all integers
dt.isUnsignedByteArray ->
array.map {
val number = it.number!!.toInt()
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for ubyte array")
val number = numValue.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isSignedByteArray ->
array.map {
val number = it.number!!.toInt()
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for byte array")
val number = numValue.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
if(number>=0) "$$hexnum" else "-$$hexnum"
}
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) ->
array.map {
when(it) {
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
}
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
is StArrayElement.StructInstance -> {
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
asmgen.asmSymbolName("$prefix.${it.name}")
}
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
}
}
dt.isSignedWordArray ->
array.map {
when(it) {
is StArrayElement.Number -> {
val number = it.value.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if (number >= 0) "$$hexnum" else "-$$hexnum"
}
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
else -> throw AssemblyError("unexpected array element type for signed word array")
}
}
dt.isLongArray ->
array.map {
when(it) {
is StArrayElement.Number -> {
val number = it.value.toInt()
val hexnum = number.absoluteValue.toLongHex()
if (number >= 0) "$$hexnum" else "-$$hexnum"
}
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
else -> throw AssemblyError("unexpected array element type for long array")
}
}
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isSignedWordArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
dt.isLongArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toLongHex()
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -1,7 +1,7 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onSuccess
import com.github.michaelbull.result.onOk
import prog8.code.*
import prog8.code.core.*
@@ -85,12 +85,12 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
result.onOk { numVariablesAllocatedInZP++ }
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// 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? ...?
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
if(errors.noErrors()) {
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
for (variable in sortedList) {
@@ -105,7 +105,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
result.onOk { numVariablesAllocatedInZP++ }
}
} else
numberOfNonIntegerVariables++
@@ -1,7 +1,9 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StConstant
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.BaseDataType
import prog8.codegen.cpu6502.AsmGen6502Internal
@@ -49,8 +51,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
init {
if(register!=null && !datatype.isNumericOrBool)
throw AssemblyError("must be numeric type")
if(register!=null) {
if(datatype.isPointer)
throw AssemblyError("register target type must be uword, not pointer $datatype $position")
else if(!datatype.isNumericOrBool)
throw AssemblyError("target type must be numeric not $datatype $position")
}
if(kind==TargetStorageKind.REGISTER)
require(register!=null)
else
@@ -192,6 +198,19 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
if(parameter?.definingAsmSub() != null)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
// Check if this identifier is a constant with a value - if so, use the value directly instead of reading from memory
val symbol = asmgen.symbolTable.lookup(value.name)
if(symbol is StConstant && symbol.value != null) {
val constValue = symbol.value!!
val baseType = when(symbol.dt.base) {
BaseDataType.POINTER -> BaseDataType.UWORD
else -> symbol.dt.base
}
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, symbol.dt,
number = PtNumber(baseType, constValue, value.position))
}
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(value.type.isUnsignedWord && varName.lowercase().startsWith("cx16.r")) {
@@ -208,19 +227,20 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is PtArrayIndexer -> {
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
}
is PtBuiltinFunctionCall -> {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
if(value.builtin) {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
} else {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name} ${value.position}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
}
else -> {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
@@ -262,7 +282,7 @@ internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
}
val target: AsmAssignTarget
get() = targets.single()
get() = targets.singleOrNull() ?: throw AssemblyError("code assumes single target but assignment is multi-value $position")
}
internal class AsmAssignment(source: AsmAssignSource,
@@ -38,96 +38,79 @@ internal class AssignmentAsmGen(
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val extsub = asmgen.symbolTable.lookup(values.name) as? StExtSub
if(extsub!=null) {
require(extsub.returns.size>=2)
if(extsub.returns.any { it.type.isFloat })
TODO("deal with (multiple?) FP return registers ${assignment.position}")
val thing = asmgen.symbolTable.lookup(values.name)
if(thing?.type== StNodeType.EXTSUB) {
val extsub = thing as StExtSub
require(extsub.returns.size>=2) {
"extsub ${extsub.name} must return at least 2 values but got ${extsub.returns.size} ${assignment.position}"
}
asmgen.translate(values)
val assignmentTargets = assignment.children.dropLast(1)
if(extsub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = extsub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if (statusFlagResults.isEmpty())
// Filter out void targets first, then separate by result type
val allResults = extsub.returns.zip(assignmentTargets)
val floatResults = allResults.filter { it.first.type.isFloat && !(it.second as PtAssignTarget).void }
val nonVoidResults = allResults.filter { !(it.second as PtAssignTarget).void && !it.first.type.isFloat }
val (statusFlagResults, registersResults) = nonVoidResults.partition { it.first.register.statusflag!=null }
val saveFlags = statusFlagResults.size > 1
// Check if we need to save A register before extracting status flags
// (flag extraction functions like assignCarryFlagResult overwrite A)
val hasByteInA = registersResults.any { (ret, _) ->
ret.type.isByteOrBool && ret.register.registerOrPair == RegisterOrPair.A
}
if(hasByteInA && statusFlagResults.isNotEmpty()) {
asmgen.out(" pha") // Save A (contains return value) before flag extraction
}
// Save status flags first (before float MOVMF or other ops clobber them)
if(statusFlagResults.isNotEmpty()) {
if(saveFlags) asmgen.out(" php")
statusFlagResults.forEach { (returns, target) ->
when(returns.register.statusflag) {
Statusflag.Pc -> assignCarryFlagResult(target as PtAssignTarget)
Statusflag.Pz -> assignZeroFlagResult(target as PtAssignTarget, saveFlags)
Statusflag.Pn -> assignNegativeFlagResult(target as PtAssignTarget, saveFlags)
Statusflag.Pv -> assignOverflowFlagResult(target as PtAssignTarget)
else -> throw AssemblyError("unknown status flag")
}
}
if(saveFlags) asmgen.out(" plp")
}
// Save float results (these use ldx/ldy/jsr which clobber flags)
floatResults.forEach { (returns, target) ->
val asmTarget = AsmAssignTarget.fromAstAssignment(target as PtAssignTarget, target.definingISub(), asmgen)
if(returns.register.registerOrPair == RegisterOrPair.FAC1) {
assignFAC1float(asmTarget)
} else if(returns.register.registerOrPair == RegisterOrPair.FAC2) {
assignFAC2float(asmTarget)
} else {
throw AssemblyError("float result must be in FAC1 or FAC2")
}
}
// Restore A if we saved it for flag extraction
if(hasByteInA && statusFlagResults.isNotEmpty()) {
asmgen.out(" pla")
}
// Handle non-float register results (not status flags)
if(registersResults.isNotEmpty()) {
assignRegisterResults(registersResults)
else if(registersResults.isEmpty())
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
else
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
}
} else {
throw AssemblyError("number of values and targets don't match")
}
} else {
val sub = asmgen.symbolTable.lookup(values.name) as? StSub
if(sub!=null) {
val scope = assignment.definingISub()
val source = AsmAssignSource.fromAstSource(assignment.children.last() as PtExpression, program, asmgen)
val targets = AsmAssignTarget.fromAstAssignmentMulti(assignment.children.dropLast(1).map { it as PtAssignTarget }, scope, asmgen)
val asmassign = AsmAssignment(source, targets, program.memsizer, assignment.position)
assignExpression(asmassign, scope)
}
else throw AssemblyError("expected extsub or normal sub")
}
}
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})
false
else
true
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
} else {
val saveA = needsToSaveA(registersResults)
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
assignRegisterResults(registersResults)
}
}
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<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
}
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")
} else if(thing?.type==StNodeType.SUBROUTINE || thing?.type==StNodeType.BUILTINFUNC) {
val scope = assignment.definingISub()
val source = AsmAssignSource.fromAstSource(assignment.children.last() as PtExpression, program, asmgen)
val targets = AsmAssignTarget.fromAstAssignmentMulti(assignment.children.dropLast(1).map { it as PtAssignTarget }, scope, asmgen)
val asmassign = AsmAssignment(source, targets, program.memsizer, assignment.position)
assignExpression(asmassign, scope)
} else throw AssemblyError("expected extsub or normal sub or builtinfunc")
}
private fun assignRegisterResults(registersResults: List<Pair<StExtSubParameter, PtNode>>) {
@@ -259,13 +242,13 @@ internal class AssignmentAsmGen(
val elementDt = assign.source.datatype
val valueVar = value.variable
if(valueVar==null) {
TODO("translate assignment of pointer ${value.position}")
TODO("translate assignment of indexed ${elementDt} pointer ${value.position}")
return
}
val arrayVarName = asmgen.asmVariableName(valueVar)
if(valueVar.type.isPointer) {
pointergen.assignIndexedPointer(assign.target, arrayVarName, value.index, valueVar.type)
pointergen.assignIndexedPointer(assign.target, arrayVarName, value, valueVar.type)
return
}
@@ -376,7 +359,11 @@ internal class AssignmentAsmGen(
if(result!=null) {
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
if(addressOfIdentifier!=null) {
val varname = asmgen.asmVariableName(addressOfIdentifier)
var varname = asmgen.asmVariableName(addressOfIdentifier)
if(addressOfIdentifier.type.isSplitWordArray) {
val addrOf = result.first as PtAddressOf
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
}
if(result.second is PtNumber) {
val offset = (result.second as PtNumber).number.toInt()
asmgen.out(" lda $varname+$offset")
@@ -537,7 +524,6 @@ internal class AssignmentAsmGen(
is PtMemoryByte -> throw AssemblyError("source kind should have been memory")
is PtTypeCast -> assignTypeCastedValue(assign.target, value.type, value.value, value)
is PtFunctionCall -> assignFunctionCall(assign, value)
is PtBuiltinFunctionCall -> assignBuiltinFunctionCall(assign.target, value)
is PtPrefix -> assignPrefixExpr(assign, value, scope)
is PtContainmentCheck -> {
containmentCheckIntoA(value)
@@ -552,8 +538,12 @@ internal class AssignmentAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
}
dt.isWord ||dt.isPointer -> {
asmgen.loadIndirectWord(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.AY, assign.target)
if(assign.target.register in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
asmgen.loadIndirectWordIntoRegisters(zpPtrVar, offset, assign.target.register!!)
} else {
asmgen.loadIndirectWordAY(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.AY, assign.target)
}
}
dt.isFloat -> {
asmgen.loadIndirectFloat(zpPtrVar, offset)
@@ -576,6 +566,12 @@ internal class AssignmentAsmGen(
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
is PtBranchCondExpression -> asmgen.assignBranchCondExpression(assign.target, value)
is PtPointerDeref -> pointergen.assignPointerDerefExpression(assign.target, value)
is PtConstant -> {
val slab = value.memorySlab
require(slab != null) { "remaining PtConstant in asmgen as assignment value can only be a memory slab reference at ${value.position}" }
val label = "$StMemorySlabBlockName.${slab.name}"
assignAddressOf(assign.target, label, false, DataType.UWORD, null)
}
else -> throw AssemblyError("weird assignment value type $value")
}
}
@@ -674,83 +670,99 @@ internal class AssignmentAsmGen(
}
}
private fun assignBuiltinFunctionCall(target: AsmAssignTarget, value: PtBuiltinFunctionCall) {
if(value.name=="peekl" && target.kind==TargetStorageKind.VARIABLE) {
val arg = value.args[0]
if(arg is PtNumber) {
val address = arg.number.toInt()
asmgen.out("""
lda $address
sta ${target.asmVarname}
lda $address+1
sta ${target.asmVarname}+1
lda $address+2
sta ${target.asmVarname}+2
lda $address+3
sta ${target.asmVarname}+3""")
return
}
else if(arg is PtIdentifier) {
val varname = asmgen.asmVariableName(arg)
if(asmgen.isZpVar(arg)) {
asmgen.out("""
ldy #0
lda ($varname),y
sta ${target.asmVarname}
iny
lda ($varname),y
sta ${target.asmVarname}+1
iny
lda ($varname),y
sta ${target.asmVarname}+2
iny
lda ($varname),y
sta ${target.asmVarname}+3""")
return
}
}
}
val returnDt = asmgen.translateBuiltinFunctionCallExpression(value, target.register)
if(target.register==null) {
// still need to assign the result to the target variable/etc.
when {
returnDt?.isByteOrBool==true -> assignRegisterByte(target, CpuRegister.A, returnDt.isSigned, false) // function's byte result is in A
returnDt?.isWord==true -> assignRegisterpairWord(target, RegisterOrPair.AY) // function's word result is in AY
returnDt==BaseDataType.STR -> {
if (target.datatype.isUnsignedWord) assignRegisterpairWord(target, RegisterOrPair.AY)
else throw AssemblyError("str return value type mismatch with target")
}
returnDt== BaseDataType.LONG -> {
// longs are in R14:R15 (r14=lsw, r15=msw)
assignRegisterLong(target, RegisterOrPair.R14R15)
}
returnDt==BaseDataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(target)
}
else -> throw AssemblyError("weird result type")
}
}
}
private fun assignFunctionCall(assign: AsmAssignment, value: PtFunctionCall) {
val symbol = asmgen.symbolTable.lookup(value.name)
val sub = symbol!!.astNode as IPtSubroutine
if(symbol!!.type==StNodeType.BUILTINFUNC) {
// builtin function call
if (value.type.isLong && assign.targets.single().kind == TargetStorageKind.VARIABLE) {
val targetVarName = assign.targets.single().asmVarname
fun handleResultInRegister(resultReg: Array<RegisterOrPair>) {
resultReg.singleOrNull()?.let {
asmgen.out(" ;--REG TO VAR")
asmgen.assignRegister(it, assign.targets[0])
asmgen.out(" ;--REG TO VAR DONE")
}
}
when(value.name) {
"peekl" -> {
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcPeekL(value, targetVarName)
return handleResultInRegister(resultInRegister)
}
"mklong" -> {
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcMklong(value, RegisterOrPair.R14R15, targetVarName)
return handleResultInRegister(resultInRegister)
}
"mklong2" -> {
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcMklong(value, RegisterOrPair.R14R15, targetVarName)
return handleResultInRegister(resultInRegister)
}
}
}
if(value.name=="lmh") {
// avoid temporary variable use when assigning lmh results
val arg = value.args.single()
if(arg is PtIdentifier) {
val (targetL, targetM, targetH) = assign.targets
val acceptedTargets = arrayOf(TargetStorageKind.VARIABLE, TargetStorageKind.VOID)
if(targetL.kind in acceptedTargets && targetM.kind in acceptedTargets && targetH.kind in acceptedTargets) {
val varname = asmgen.asmVariableName(arg)
if(targetL.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname | sta ${targetL.asmVarname}")
if(targetM.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname+1 | sta ${targetM.asmVarname}")
if(targetH.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname+2 | sta ${targetH.asmVarname}")
return
}
}
}
// TODO optimized float functions into variable? (to avoid needless FAC1 register copying?)
val firstTarget = assign.targets.firstOrNull()
val actualResultRegisters = asmgen.translateBuiltinFunctionCallExpression(value, firstTarget?.register)
require(actualResultRegisters.size==assign.targets.size) { "builtin function call should have same number return values as assignment targets ${value.position}" }
// assign.targets.zip(actualResultRegisters).forEach { (target, register) ->
// if(target.kind==TargetStorageKind.REGISTER) {
// if(target.register!! != register) {
// println("OPTIMIZE: ${value.name} builtin func output register mismatch ${target.register} got $register ${value.position}")
// }
// }
// }
if(assign.targets.size>1) {
assign.targets.zip(actualResultRegisters)
.filter { it.first.kind != TargetStorageKind.VOID }
.forEach { target -> asmgen.assignRegister(target.second, target.first) }
} else if(actualResultRegisters.isNotEmpty() && assign.targets.single().kind != TargetStorageKind.VOID) {
val target = assign.targets.single()
if(target.kind != TargetStorageKind.VOID) {
asmgen.assignRegister(actualResultRegisters.single(), target)
}
}
return
}
// regular subroutine call
val sub = symbol.astNode as IPtSubroutine
val returns = sub.returnsWhatWhere()
asmgen.translateFunctionCall(value)
if(sub is PtSub && sub.signature.returns.size>1) {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
val returnRegs = sub.returnsWhatWhere()
assign.targets.zip(returnRegs).forEach { target ->
assign.targets.zip(returns).forEach { target ->
if(target.first.kind != TargetStorageKind.VOID) {
asmgen.assignRegister(target.second.first.registerOrPair!!, target.first)
}
}
} else {
val target = assign.target
val returnValue = sub.returnsWhatWhere().singleOrNull { it.first.registerOrPair!=null } ?: sub.returnsWhatWhere().single { it.first.statusflag!=null }
if(target.kind==TargetStorageKind.VOID)
return
val returnValue = returns.singleOrNull { it.first.registerOrPair!=null } ?: returns.single { it.first.statusflag!=null }
when {
returnValue.second.isString -> {
val targetDt = target.datatype
@@ -856,8 +868,8 @@ internal class AssignmentAsmGen(
return if(translatedOk)
true
else {
if(expr.type.isLong && expr.operator in "*/%")
TODO("long multiplication or division - use floats for now ${expr.position} ")
if(expr.type.isLong && expr.operator in "/%")
TODO("long division - use floats for now ${expr.position} ")
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
}
@@ -992,12 +1004,12 @@ internal class AssignmentAsmGen(
&& expr.right.type.isWord) {
// word X>Y -> X<Y, X<=Y -> Y>=X , easier to do in 6502 (codegen also expects these to no longe exist!)
exprClone = PtBinaryExpression(if(expr.operator==">") "<" else ">=", expr.type, expr.position)
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
} else {
exprClone = PtBinaryExpression(expr.operator, expr.type, expr.position)
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
}
ifelse.add(exprClone)
ifelse.add(ifPart)
@@ -1080,12 +1092,12 @@ internal class AssignmentAsmGen(
&& expr.right.type.isWord) {
// word X>Y -> X<Y, X<=Y -> Y>=X , easier to do in 6502 (codegen also expects these to no longe exist!)
exprClone = PtBinaryExpression(if(expr.operator==">") "<" else ">=", expr.type, expr.position)
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
} else {
exprClone = PtBinaryExpression(expr.operator, expr.type, expr.position)
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
}
ifelse.add(exprClone)
ifelse.add(ifPart)
@@ -1100,7 +1112,7 @@ internal class AssignmentAsmGen(
is PtIdentifier -> true
is PtIrRegister -> true
is PtNumber -> true
is PtBuiltinFunctionCall -> expr.name in arrayOf("lsb", "msb")
is PtFunctionCall -> expr.builtin && expr.name in arrayOf("lsb", "msb")
else -> false
}
}
@@ -1125,6 +1137,30 @@ internal class AssignmentAsmGen(
assignVariableWord(target, "P8ZP_SCRATCH_W2", DataType.UWORD)
return true
}
expr.type.isSignedByte -> {
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
return true
// TODO implement the signed remainder asm routine
// assignExpressionToRegister(expr.left, RegisterOrPair.A, true)
// if(!directIntoY(expr.right)) asmgen.out(" pha")
// assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
// if(!directIntoY(expr.right)) asmgen.out(" pla")
// asmgen.out(" jsr prog8_math.divmod_b_asm")
// if(target.register==RegisterOrPair.A)
// asmgen.out(" cmp #0") // fix the status register
// else
// assignRegisterByte(target, CpuRegister.A, true, true)
// return true
}
expr.type.isSignedWord -> {
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
return true
// TODO implement the signed remainder asm routine
// asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
// asmgen.out(" jsr prog8_math.divmod_w_asm")
// assignVariableWord(target, "cx16.r15", DataType.WORD)
// return true
}
else -> return false
}
}
@@ -1204,6 +1240,13 @@ internal class AssignmentAsmGen(
}
return true
}
expr.type.isLong -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, true)
asmgen.out(" jsr prog8_math.multiply_longs")
assignRegisterLong(target, RegisterOrPair.R14R15)
return true
}
else -> return false
}
} else {
@@ -1267,6 +1310,17 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
expr.type.isLong -> {
// long * constant
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
asmgen.out(" lda #<${value} | sta cx16.r14")
asmgen.out(" lda #>${value} | sta cx16.r14+1")
asmgen.out(" lda #<(${value}>>16) | sta cx16.r15")
asmgen.out(" lda #>(${value}>>16) | sta cx16.r15+1")
asmgen.out(" jsr prog8_math.multiply_longs")
assignRegisterLong(target, RegisterOrPair.R14R15)
return true
}
else -> return false
}
}
@@ -1486,8 +1540,30 @@ internal class AssignmentAsmGen(
return false
}
private fun getIdentForNoopCastOrIdent(expr: PtExpression): Pair<String, DataType>? {
// Returns the variable name and type if the expression is:
// 1. A plain identifier (returns its name and type)
// 2. A no-op type cast (word<->uword) - unwraps and returns the inner identifier
// Otherwise returns null
val identifier = when (expr) {
is PtIdentifier -> expr
is PtTypeCast -> {
// Check if this is a no-op cast (word<->uword)
if (expr.value is PtIdentifier &&
((expr.type.isWord && expr.value.type.isUnsignedWord) ||
(expr.type.isUnsignedWord && expr.value.type.isWord) ||
(expr.type.isByte && expr.value.type.isByte))) {
expr.value as PtIdentifier
} else {
return null
}
}
else -> return null
}
return Pair(asmgen.asmVariableName(identifier), identifier.type)
}
private fun optimizedPlusMinExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
// TODO can this use the target directly as intermediate, when it's a variable? To save the final assignRegister call.
val dt = expr.type
val left = expr.left
val right = expr.right
@@ -1583,32 +1659,42 @@ internal class AssignmentAsmGen(
} else if(dt.isWord || dt.isPointer) {
fun doAddOrSubWordExpr() {
if(expr.operator=="+" && expr.left.type.isWord && expr.left is PtIdentifier) {
val symname = asmgen.asmVariableName(expr.left as PtIdentifier)
val leftAddr = asmgen.getStaticAddressLowHigh(expr.left)
val rightAddr = asmgen.getStaticAddressLowHigh(expr.right)
if (expr.operator == "+" && leftAddr != null) {
assignExpressionToRegister(expr.right, RegisterOrPair.AY, expr.right.type.isSigned)
asmgen.out("""
clc
adc $symname
adc ${leftAddr.first}
tax
tya
adc $symname+1
adc ${leftAddr.second}
tay
txa""")
}
else if(expr.operator=="-" && expr.right.type.isWord && expr.right is PtIdentifier) {
val symname = asmgen.asmVariableName(expr.right as PtIdentifier)
} else if (expr.operator == "+" && rightAddr != null) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.left.type.isSigned)
asmgen.out("""
clc
adc ${rightAddr.first}
tax
tya
adc ${rightAddr.second}
tay
txa""")
} else if (expr.operator == "-" && rightAddr != null) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.left.type.isSigned)
asmgen.out("""
sec
sbc $symname
sbc ${rightAddr.first}
tax
tya
sbc $symname+1
sbc ${rightAddr.second}
tay
txa""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
if(expr.operator=="+")
if (expr.operator == "+")
asmgen.out("""
clc
adc P8ZP_SCRATCH_W1
@@ -1630,6 +1716,66 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(target, RegisterOrPair.AY)
}
// Handle when left is PtAddressOf of a split word array
val leftAddressOf = left as? PtAddressOf
if(leftAddressOf!=null && leftAddressOf.identifier!=null && !leftAddressOf.isFromArrayElement && leftAddressOf.dereference==null) {
if(leftAddressOf.identifier!!.type.isSplitWordArray) {
var symbol = asmgen.asmVariableName(leftAddressOf.identifier!!)
symbol = if(leftAddressOf.isMsbForSplitArray) symbol+"_msb" else symbol+"_lsb"
assignExpressionToRegister(right, RegisterOrPair.AY, right.type.isSigned)
if(expr.operator=="+")
asmgen.out("""
clc
adc #<$symbol
tax
tya
adc #>$symbol
tay
txa""")
else
asmgen.out("""
sec
sbc #<$symbol
tax
tya
sbc #>$symbol
tay
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
}
// Memory-to-memory optimization: when both operands and target are static variables
// Also handles no-op type casts (word<->uword) by unwrapping them
if(target.kind == TargetStorageKind.VARIABLE) {
val leftInfo = getIdentForNoopCastOrIdent(expr.left)
val rightInfo = getIdentForNoopCastOrIdent(expr.right)
if(leftInfo!=null && rightInfo!=null) {
val targetSym = target.asmVarname
if(expr.operator=="+") {
asmgen.out("""
lda ${leftInfo.first}
clc
adc ${rightInfo.first}
sta $targetSym
lda ${leftInfo.first}+1
adc ${rightInfo.first}+1
sta $targetSym+1""")
} else {
asmgen.out("""
lda ${leftInfo.first}
sec
sbc ${rightInfo.first}
sta $targetSym
lda ${leftInfo.first}+1
sbc ${rightInfo.first}+1
sta $targetSym+1""")
}
return true
}
}
when (right) {
is PtAddressOf -> {
if(right.isFromArrayElement) {
@@ -1688,6 +1834,34 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
is PtConstant -> {
val addr = asmgen.getStaticAddressLowHigh(right)
if (addr != null) {
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
if (expr.operator == "+")
asmgen.out("""
clc
adc ${addr.first}
tax
tya
adc ${addr.second}
tay
txa""")
else
asmgen.out("""
sec
sbc ${addr.first}
tax
tya
sbc ${addr.second}
tay
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
doAddOrSubWordExpr()
return true
}
is PtNumber -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
@@ -2231,7 +2405,32 @@ internal class AssignmentAsmGen(
assignExpressionToRegister(left, RegisterOrPair.A, false)
when(right) {
is PtBool -> throw AssemblyError("bool literal in logical expr should have been optimized away")
is PtBool -> {
// Handle boolean literals that weren't optimized away (e.g., with -noopt)
when {
operator == "and" && right.value -> {
// left AND true = left (already in A, do nothing)
}
operator == "and" && !right.value -> {
// left AND false = false
asmgen.out(" lda #0")
}
operator == "or" && right.value -> {
// left OR true = true
asmgen.out(" lda #1")
}
operator == "or" && !right.value -> {
// left OR false = left (already in A, do nothing)
}
operator == "xor" && right.value -> {
// left XOR true = NOT left (invert low bit)
asmgen.out(" eor #1")
}
operator == "xor" && !right.value -> {
// left XOR false = left (already in A, do nothing)
}
}
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(right)
when (operator) {
@@ -2278,10 +2477,13 @@ internal class AssignmentAsmGen(
fun requiresCmp(expr: PtExpression) =
when (expr) {
is PtFunctionCall -> {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
if(expr.builtin)
true
else {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
}
}
is PtBuiltinFunctionCall -> true
is PtIfExpression -> true
else -> false
}
@@ -2968,7 +3170,7 @@ $endLabel""")
}
private fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) {
val lsb = PtBuiltinFunctionCall("lsb", false, true, DataType.UBYTE, value.position)
val lsb = PtFunctionCall("lsb", true, true,arrayOf(DataType.UBYTE), value.position)
lsb.parent = value.parent
lsb.add(value)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
@@ -2977,7 +3179,7 @@ $endLabel""")
}
private fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) {
val lsb = PtBuiltinFunctionCall("lsw", false, true, DataType.UWORD, value.position)
val lsb = PtFunctionCall("lsw", true, true, arrayOf(DataType.UWORD), value.position)
lsb.parent = value.parent
lsb.add(value)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb)
@@ -3517,7 +3719,7 @@ $endLabel""")
asmgen.out(" asl a")
}
} else if(subtype==BaseDataType.FLOAT) {
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5u)
TODO("support float size other than 5 ${arrayIndexExpr.position}")
asmgen.out("""
sta P8ZP_SCRATCH_REG
@@ -3610,15 +3812,13 @@ $endLabel""")
TODO("ubyte to long var ${target.position}")
}
DataType.LONG -> {
// Long to long variable copy - use loop
asmgen.out("""
lda $varName
sta ${target.asmVarname}
lda $varName+1
sta ${target.asmVarname}+1
lda $varName+2
sta ${target.asmVarname}+2
lda $varName+3
sta ${target.asmVarname}+3""")
ldy #3
- lda $varName,y
sta ${target.asmVarname},y
dey
bpl -""")
}
else -> throw AssemblyError("wrong dt ${target.position}")
}
@@ -3756,7 +3956,12 @@ $endLabel""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool || sourceDt.isPointer) { "weird source dt for word variable" }
require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool || sourceDt.isPointer) {
if(sourceDt.isString)
"str source type for word variable should have been uword or ^^ubyte, a && is likely missing on the source variable $sourceDt ${target.position}"
else
"weird source dt for word variable: $sourceDt ${target.position}"
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(sourceDt.isUnsignedByte || sourceDt.isBool) {
@@ -4237,14 +4442,11 @@ $endLabel""")
if(pairedRegisters in CombinedLongRegisters) {
val startreg = pairedRegisters.startregname()
asmgen.out("""
lda cx16.$startreg
sta ${target.asmVarname}
lda cx16.$startreg+1
sta ${target.asmVarname}+1
lda cx16.$startreg+2
sta ${target.asmVarname}+2
lda cx16.$startreg+3
sta ${target.asmVarname}+3""")
ldy #3
- lda cx16.$startreg,y
sta ${target.asmVarname},y
dey
bpl -""")
}
else throw AssemblyError("only combined vreg allowed as long target ${target.position}")
}
@@ -4252,6 +4454,7 @@ $endLabel""")
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
val arrayVarName = asmgen.asmSymbolName(target.array.variable!!)
val startreg = pairedRegisters.startregname()
// Unrolled copy to preserve array index in Y
asmgen.out("""
lda cx16.$startreg
sta $arrayVarName,y
@@ -4269,15 +4472,13 @@ $endLabel""")
if(targetreg!=pairedRegisters) {
val sourceStartReg = pairedRegisters.startregname()
val targetStartReg = targetreg.startregname()
// Loop-based copy between register pairs
asmgen.out("""
lda cx16.$sourceStartReg
sta cx16.$targetStartReg
lda cx16.$sourceStartReg+1
sta cx16.$targetStartReg+1
lda cx16.$sourceStartReg+2
sta cx16.$targetStartReg+2
lda cx16.$sourceStartReg+3
sta cx16.$targetStartReg+3""")
ldy #3
- lda cx16.$sourceStartReg,y
sta cx16.$targetStartReg,y
dey
bpl -""")
}
}
TargetStorageKind.POINTER -> {
@@ -5312,7 +5513,11 @@ $endLabel""")
} else if(addressOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addressOf.position}")
} else {
asmgen.out(" sta ${asmgen.asmSymbolName(addressOf.identifier!!)}")
var symbolName = asmgen.asmSymbolName(addressOf.identifier!!)
if(addressOf.identifier!!.type.isSplitWordArray) {
symbolName = if(addressOf.isMsbForSplitArray) symbolName+"_msb" else symbolName+"_lsb"
}
asmgen.out(" sta $symbolName")
}
}
addressExpr is PtIdentifier -> {
@@ -5323,7 +5528,11 @@ $endLabel""")
if(result!=null) {
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
if(addressOfIdentifier!=null) {
val varname = asmgen.asmVariableName(addressOfIdentifier)
var varname = asmgen.asmVariableName(addressOfIdentifier)
if(addressOfIdentifier.type.isSplitWordArray) {
val addrOf = result.first as PtAddressOf
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
}
if(result.second is PtNumber) {
val offset = (result.second as PtNumber).number.toInt()
asmgen.out(" sta $varname+$offset")
@@ -5364,7 +5573,7 @@ $endLabel""")
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, pos: Position) {
val tgt = AsmAssignTarget.fromRegisters(register, signed, pos, null, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, Position.DUMMY)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, pos)
translateNormalAssignment(assign, scope)
}
@@ -44,7 +44,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"^=", "xor=" -> inplaceModification(assign.target, "^", assign.source)
"<<=" -> inplaceModification(assign.target, "<<", assign.source)
">>=" -> inplaceModification(assign.target, ">>", assign.source)
"%=" -> inplaceModification(assign.target, "%", assign.source)
"%=" -> {
if(assign.source.datatype.isSigned) {
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", assign.position)
// TODO implement the signed remainder asm routine
}
else
inplaceModification(assign.target, "%", assign.source)
}
"==" -> inplaceModification(assign.target, "==", assign.source)
"!=" -> inplaceModification(assign.target, "!=", assign.source)
"<" -> inplaceModification(assign.target, "<", assign.source)
@@ -572,34 +579,22 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"+" -> {
asmgen.out("""
clc
lda $targetVar
adc $sourceVar
sta $targetVar
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3""")
ldx #252
- lda $targetVar+4,x
adc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
}
"-" -> {
asmgen.out("""
sec
lda $targetVar
sbc $sourceVar
sta $targetVar
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3""")
ldx #252
- lda $targetVar+4,x
sbc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
}
"<<" -> {
asmgen.out("""
@@ -625,51 +620,55 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"|" -> {
asmgen.out("""
lda $targetVar
ora $sourceVar
sta $targetVar
lda $targetVar+1
ora $sourceVar+1
sta $targetVar+1
lda $targetVar+2
ora $sourceVar+2
sta $targetVar+2
lda $targetVar+3
ora $sourceVar+3
sta $targetVar+3""")
ldy #3
- lda $targetVar,y
ora $sourceVar,y
sta $targetVar,y
dey
bpl -""")
}
"&" -> {
asmgen.out("""
lda $targetVar
and $sourceVar
sta $targetVar
lda $targetVar+1
and $sourceVar+1
sta $targetVar+1
lda $targetVar+2
and $sourceVar+2
sta $targetVar+2
lda $targetVar+3
and $sourceVar+3
sta $targetVar+3""")
ldy #3
- lda $targetVar,y
and $sourceVar,y
sta $targetVar,y
dey
bpl -""")
}
"^" -> {
asmgen.out("""
lda $targetVar
eor $sourceVar
sta $targetVar
lda $targetVar+1
eor $sourceVar+1
sta $targetVar+1
lda $targetVar+2
eor $sourceVar+2
sta $targetVar+2
lda $targetVar+3
eor $sourceVar+3
sta $targetVar+3""")
ldy #3
- lda $targetVar,y
eor $sourceVar,y
sta $targetVar,y
dey
bpl -""")
}
"*" -> {
asmgen.out("""
; Copy target to R12R13 (multiplicand)
ldy #3
- lda $targetVar,y
sta cx16.r12,y
dey
bpl -
; Copy source to R14R15 (multiplier)
ldy #3
- lda $sourceVar,y
sta cx16.r14,y
dey
bpl -
jsr prog8_math.multiply_longs
; Copy result from R14R15 back to target
ldy #3
- lda cx16.r14,y
sta $targetVar,y
dey
bpl -""")
}
else -> {
TODO("in-place modify LONG with variable")
TODO("in-place modify LONG with variable, operator=$operator")
}
}
}
@@ -994,6 +993,34 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
"*" -> {
// long *= constant - use multiplication routine with loop-based copies
val lo = value and 0xFFFF
val hi = (value ushr 16) and 0xFFFF
asmgen.out("""
; Copy variable to R12R13 (multiplicand)
ldy #3
- lda $variable,y
sta cx16.r12,y
dey
bpl -
; Load constant into R14R15 (multiplier)
lda #<${lo}
sta cx16.r14
lda #>${lo}
sta cx16.r14+1
lda #<${hi}
sta cx16.r15
lda #>${hi}
sta cx16.r15+1
jsr prog8_math.multiply_longs
; Copy result from R14R15 back to variable
ldy #3
- lda cx16.r14,y
sta $variable,y
dey
bpl -""")
}
"<<" -> if (value > 0) inplaceLongShiftLeft()
">>" -> if (value > 0) inplaceLongShiftRight()
"|" -> {
@@ -1135,9 +1162,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val assignValue = AsmAssignment(value,
listOf(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE,
address.definingISub(), Position.DUMMY, register = RegisterOrPair.A)
address.definingISub(), address.position, register = RegisterOrPair.A)
),
program.memsizer, Position.DUMMY)
program.memsizer, address.position)
assignmentAsmGen.translateNormalAssignment(assignValue, address.definingISub()) // calculate value into A
}
@@ -1220,6 +1247,33 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sbc cx16.r0H
sta ${arrayVar}_msb+$index""")
}
"|" -> {
asmgen.out("""
lda ${arrayVar}_lsb+$index
ora cx16.r0L
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
ora cx16.r0H
sta ${arrayVar}_msb+$index""")
}
"&" -> {
asmgen.out("""
lda ${arrayVar}_lsb+$index
and cx16.r0L
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
and cx16.r0H
sta ${arrayVar}_msb+$index""")
}
"^" -> {
asmgen.out("""
lda ${arrayVar}_lsb+$index
eor cx16.r0L
sta ${arrayVar}_lsb+$index
lda ${arrayVar}_msb+$index
eor cx16.r0H
sta ${arrayVar}_msb+$index""")
}
else -> TODO("inplace split word array value $operator")
}
}
@@ -1528,7 +1582,7 @@ $shortcutLabel:""")
}
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
if(operator in "+-" && variable in arrayOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
if(operator in "+-" && variable in setOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
if(operator=="+")
asmgen.out(" inc a")
@@ -1710,8 +1764,8 @@ $shortcutLabel:""")
private fun inplacemodificationRegisterAwithVariableWithSwappedOperands(operator: String, variable: String, signed: Boolean) {
// A = variable <operator> A
if(operator in AssociativeOperators)
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for associative operators
if(operator in CommutativeOperators)
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for commutative operators
// now implement the non-assiciative operators...
when (operator) {
@@ -242,14 +242,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
val (zpPtrVar, offset) = deref(value)
val (zpPtrVar, offset1) = deref(value)
val offset: UByte
if(value.derefLast) {
asmgen.loadIndirectWordAY(zpPtrVar, offset1)
asmgen.out(" sta $zpPtrVar | sty $zpPtrVar+1")
offset = 0.toUByte()
} else {
offset = offset1
}
if(value.type.isByteOrBool) {
asmgen.loadIndirectByte(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.A, target)
}
else if(value.type.isWord || value.type.isPointer) {
asmgen.loadIndirectWord(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.AY, target)
if(target.register in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
asmgen.loadIndirectWordIntoRegisters(zpPtrVar, offset, target.register!!)
} else {
asmgen.loadIndirectWordAY(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.AY, target)
}
}
else if(value.type.isFloat) {
asmgen.loadIndirectFloat(zpPtrVar, offset)
@@ -270,15 +282,17 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
sta ${target.asmVarname}+2
iny
lda ($zpPtrVar),y
sta ${target.asmVarname}+3
""")
sta ${target.asmVarname}+3""")
}
TargetStorageKind.REGISTER -> {
require(target.register!! in CombinedLongRegisters)
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, target.register)
}
TargetStorageKind.POINTER -> {
// TODO optimize this so it doesn't need a temporary long var all the time
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
asmgen.assignRegister(RegisterOrPair.R14R15, target)
}
TargetStorageKind.ARRAY -> {
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
asmgen.assignRegister(RegisterOrPair.R14R15, target)
}
@@ -1738,7 +1752,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
fun multiply() {
// on entry here: number placed in routine argument variable
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
jsr prog8_math.multiply_words
tax
@@ -1807,7 +1821,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val number = value.number!!.number.toInt()
if(number in powersOfTwoInt)
throw AssemblyError("divide by power of two should have been a shift $value.position")
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1818,7 +1832,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1830,7 +1844,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val register = value.register!!
require(register.isWord())
val regname = asmgen.asmSymbolName(register)
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1839,7 +1853,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
divide(target.dt.isSigned)
}
SourceStorageKind.EXPRESSION -> {
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
divide(target.dt.isSigned)
@@ -1869,7 +1883,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val number = value.number!!.number.toInt()
if(number in powersOfTwoInt)
throw AssemblyError("divide by power of two should have been a shift $value.position")
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1880,7 +1894,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1892,7 +1906,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val register = value.register!!
require(register.isWord())
val regname = asmgen.asmSymbolName(register)
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -1901,7 +1915,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
modulus()
}
SourceStorageKind.EXPRESSION -> {
asmgen.loadIndirectWord(zpPtrVar, 0u)
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
modulus()
@@ -2546,46 +2560,42 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
// when {
// target.datatype.isByteOrBool -> {
// asmgen.out("""
// ldy #0
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.A, target)
// }
// target.datatype.isWord || target.datatype.isPointer -> {
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// lda (P8ZP_SCRATCH_PTR)""")
// else
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// dey
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.AX, target)
// }
// target.datatype.isLong -> {
// TODO("assign long from pointer to $target ${target.position}")
// }
// target.datatype.isFloat -> {
// // TODO optimize the float copying to avoid having to go through FAC1
// asmgen.out("""
// lda P8ZP_SCRATCH_PTR
// ldy P8ZP_SCRATCH_PTR+1
// jsr floats.MOVFM""")
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
// }
// else -> throw AssemblyError("weird dt ${target.datatype}")
// }
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
// Load value from array[index] and store to pointer target
if(target.datatype.isLong) {
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
val index = arrayIndexer.index
asmgen.assignExpressionToRegister(index, RegisterOrPair.AY, false)
asmgen.out(" sty P8ZP_SCRATCH_REG")
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *2
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *4
asmgen.out(" ldy P8ZP_SCRATCH_REG")
asmgen.out("""
clc
adc $arrayVarName
pha
tya
adc $arrayVarName+1
tay
pla""")
// Now AY = address of array[index], save to scratch pointer
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
// Get target pointer address
target.pointer?.let { ptr ->
val (ptrVar, offset) = deref(ptr)
// 4-byte copy loop from (P8ZP_SCRATCH_W1) to (ptrVar)+offset
asmgen.out("""
ldx #4
ldy #$offset
- lda (P8ZP_SCRATCH_W1),y
sta ($ptrVar),y
iny
dex
bne -""")
} ?: throw AssemblyError("expected pointer target ${target.position}")
} else {
throw AssemblyError("unsupported type for indexed pointer assignment ${target.datatype} ${target.position}")
}
}
private fun saveOnStack(regs: RegisterOrPair) {
-93
View File
@@ -1,93 +0,0 @@
package prog8tests.codegencpu6502
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray) {
require(numElements != null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
val infos = mutableListOf<String>()
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in errors)
errors.add(text)
}
override fun warn(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
infos.add(text)
}
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
infos.clear()
}
}
+591
View File
@@ -0,0 +1,591 @@
package prog8tests.codegencpu6502
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8.codegen.cpu6502.*
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
class TestAsmOptimizer: FunSpec({
val machine = C64Target()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val symbolTable = SymbolTable(program)
// Add test variables with known non-IO addresses so the optimizer can resolve them
// Non-IO addresses in typical C64 RAM range
symbolTable.add(StMemVar("myvar", DataType.UBYTE, 0x0300u, null, null))
symbolTable.add(StMemVar("var", DataType.UBYTE, 0x0301u, null, null))
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0302u, null, null))
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0304u, null, null))
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0306u, null, null))
symbolTable.add(StMemVar("P8ZP_SCRATCH_PTR", DataType.UWORD, 0x02u, null, null))
symbolTable.add(StMemVar("P8ZP_SCRATCH_W1", DataType.UWORD, 0x04u, null, null))
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0310u, null, null))
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0312u, null, null))
fun optimize(lines: MutableList<String>): Int {
return optimizeAssembly(lines, machine, symbolTable)
}
// --- optimizeIncDec ---
test("optimizeIncDec: removes iny+dey sequence") {
val lines = mutableListOf(" iny", " dey", " rts", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes iny+dey sequence with comments") {
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes inx+dex sequence") {
val lines = mutableListOf(" inx", " dex", " rts", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes dey+iny sequence") {
val lines = mutableListOf(" dey", " iny", " rts", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes dex+inx sequence") {
val lines = mutableListOf(" dex", " inx", " rts", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: preserves non-canceling sequences") {
val lines = mutableListOf(" iny", " inx", " rts", " nop")
optimize(lines) shouldBe 0
lines shouldBe listOf(" iny", " inx", " rts", " nop")
}
// --- optimizeStoreLoadSame ---
test("optimizeStoreLoadSame: removes sta X + lda X") {
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
}
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
// Note: comments don't affect the optimization, but the optimizer checks lines[1] and lines[2]
// and the comment is part of the line, so "sta myvar ; store it" doesn't match "sta myvar"
// The optimizer uses substring(4) which includes the comment
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
optimize(lines) shouldBe 1
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
}
test("optimizeStoreLoadSame: removes pha + pla") {
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
val lines = mutableListOf(" nop", " pha", " pla", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes phx + plx") {
val lines = mutableListOf(" nop", " phx", " plx", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes phy + ply") {
val lines = mutableListOf(" nop", " phy", " ply", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes lda X + sta X") {
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" nop", " lda myvar", " rts")
}
// --- optimizeJsrRtsAndOtherCombinations ---
test("optimizeJsrRts: converts jsr+rts to jmp") {
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" jmp mysub", " nop", " nop")
}
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
}
test("optimizeJsrRts: removes rts+jmp") {
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop", " nop")
}
test("optimizeJsrRts: removes rts+bra") {
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts", " nop", " nop")
}
test("optimizeJsrRts: removes lda+cmp #0") {
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
}
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
}
test("optimizeJsrRts: removes bra+jmp") {
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" bra somewhere", " nop", " nop")
}
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
optimize(lines) shouldBe 0
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
}
// --- optimizeUselessPushPopStack ---
test("optimizeUselessPushPop: removes phy+ldy+pla") {
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" tya", " ldy #5", " rts")
}
test("optimizeUselessPushPop: removes phx+ldx+pla") {
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" txa", " ldx #5", " rts")
}
test("optimizeUselessPushPop: removes pha+lda+pla") {
// Note: optimizer removes all three: pha, lda, and pla
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts")
}
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" rts")
}
// --- optimizeUnneededTempvarInAdd ---
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" clc", " adc #5", " rts")
}
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
optimize(lines) shouldBe 1
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
}
// --- optimizeTSBtoRegularOr ---
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
optimize(lines) shouldBe 1
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
}
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
optimize(lines) shouldBe 1
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
}
// --- optimizeSameAssignments ---
test("optimizeSameAssignments: removes duplicate lda/ldy/sta/sty word init") {
val lines = mutableListOf(
" lda #1", " ldy #0", " sta var1", " sty var1+1",
" lda #1", " ldy #0", " sta var2", " sty var2+1",
" rts", " nop", " nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
lines shouldBe listOf(
" lda #1", " ldy #0", " sta var1", " sty var1+1",
" sta var2", " sty var2+1",
" rts", " nop", " nop", " nop", " nop", " nop"
)
}
// --- optimizeSamePointerIndexingAndUselessBeq ---
test("optimizeSamePointerIndexing: removes redundant ldy in pointer indexing") {
val lines = mutableListOf(
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
// --- optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore ---
test("optimizeAddWordToSameVariable: optimizes P8ZP_SCRATCH_PTR += AY") {
val lines = mutableListOf(
" clc", " adc P8ZP_SCRATCH_PTR", " pha", " tya",
" adc P8ZP_SCRATCH_PTR+1", " tay", " pla",
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
" nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
lines shouldBe listOf(
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
" nop", " nop", " nop", " nop"
)
}
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
optimize(lines) shouldBe 1
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
// --- Edge cases ---
test("handles empty input") {
val lines = mutableListOf<String>()
optimize(lines) shouldBe 0
lines shouldBe emptyList()
}
test("handles single line input") {
val lines = mutableListOf(" rts")
optimize(lines) shouldBe 0
lines shouldBe listOf(" rts")
}
test("multiple optimizations in one pass") {
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
optimize(lines) shouldBe 2
lines shouldBe listOf(" lda #1", " sta var", " rts")
}
// --- Helper function tests ---
// hasInstr tests
test("hasInstr: exact match") {
"iny".hasInstr("iny") shouldBe true
"pla".hasInstr("pla") shouldBe true
"rts".hasInstr("rts") shouldBe true
}
test("hasInstr: starts with mnemonic and space") {
"lda #1".hasInstr("lda") shouldBe true
"sta myvar".hasInstr("sta") shouldBe true
"ldy #0".hasInstr("ldy") shouldBe true
"inc a".hasInstr("inc") shouldBe true
}
test("hasInstr: contains mnemonic after label") {
"mylabel: lda #1".hasInstr("lda") shouldBe true
"loop: iny".hasInstr("iny") shouldBe true
"skip: rts".hasInstr("rts") shouldBe true
}
test("hasInstr: does not match named labels without colon") {
// Named labels require a colon to distinguish from instructions
"mylabel lda #1".hasInstr("lda") shouldBe false
"loop iny".hasInstr("iny") shouldBe false
"skip rts".hasInstr("rts") shouldBe false
}
test("hasInstr: handles 64tass anonymous labels with colon") {
"+: lda #1".hasInstr("lda") shouldBe true
"++: lda #1".hasInstr("lda") shouldBe true
"-: lda #1".hasInstr("lda") shouldBe true
"--: lda #1".hasInstr("lda") shouldBe true
"+: rts".hasInstr("rts") shouldBe true
"+++: iny".hasInstr("iny") shouldBe true
}
test("hasInstr: handles 64tass anonymous labels without colon") {
"+ lda #1".hasInstr("lda") shouldBe true
"++ lda #1".hasInstr("lda") shouldBe true
"- lda #1".hasInstr("lda") shouldBe true
"-- lda #1".hasInstr("lda") shouldBe true
"+ rts".hasInstr("rts") shouldBe true
"+++ iny".hasInstr("iny") shouldBe true
}
test("hasInstr: handles scoped labels with periods") {
"mysub.loop: lda #1".hasInstr("lda") shouldBe true
"_private: rts".hasInstr("rts") shouldBe true
// Without colon, named labels are not recognized
"mysub.loop lda #1".hasInstr("lda") shouldBe false
"_private rts".hasInstr("rts") shouldBe false
}
test("hasInstr: does not match numbers as labels") {
"1 lda #1".hasInstr("lda") shouldBe false
"+1 lda #1".hasInstr("lda") shouldBe false
"-1 lda #1".hasInstr("lda") shouldBe false
}
test("hasInstr: does not match partial mnemonics") {
"lda #1".hasInstr("ld") shouldBe false
"sta var".hasInstr("st") shouldBe false
"iny".hasInstr("in") shouldBe false
}
test("hasInstr: does not match mnemonic in operand") {
// Note: hasInstr uses contains(" mnemonic") which can match operands
// This is a known limitation - the optimizer only calls hasInstr on
// trimmed lines where the instruction is at the start or after a label
"lda #iny".hasInstr("iny") shouldBe false
// "sta pla" would match due to contains(" pla") - this is expected behavior
// in the optimizer context where lines are properly formatted
}
test("hasInstr: handles inc a variants") {
"inc a".hasInstr("inc a") shouldBe true
"inc a".hasInstr("inc a") shouldBe true
"inc var".hasInstr("inc a") shouldBe false
}
test("hasInstr: handles lines with trailing comments") {
"iny ; increment Y".hasInstr("iny") shouldBe true
"lda #1 ; load immediate".hasInstr("lda") shouldBe true
"mylabel: rts ; return".hasInstr("rts") shouldBe true
// Note: hasInstr expects trimmed lines, so leading whitespace must be removed first
"dey ; decrement Y".hasInstr("dey") shouldBe true
}
test("hasInstr: handles dec a variants") {
"dec a".hasInstr("dec a") shouldBe true
"dec a".hasInstr("dec a") shouldBe true
"dec var".hasInstr("dec a") shouldBe false
}
// isBranch tests
test("isBranch: matches branch instructions") {
"beq skip".isBranch() shouldBe true
"bne loop".isBranch() shouldBe true
"bcc target".isBranch() shouldBe true
"bcs target".isBranch() shouldBe true
"bmi neg".isBranch() shouldBe true
"bpl pos".isBranch() shouldBe true
"bvc noov".isBranch() shouldBe true
"bvs ov".isBranch() shouldBe true
"bra somewhere".isBranch() shouldBe true
}
test("isBranch: matches branch instructions with trailing comments") {
"beq skip ; branch if equal".isBranch() shouldBe true
"bne loop ; branch if not equal".isBranch() shouldBe true
"bra target ; always branch".isBranch() shouldBe true
}
test("isBranch: does not match non-branch instructions") {
"lda #1".isBranch() shouldBe false
"sta var".isBranch() shouldBe false
"rts".isBranch() shouldBe false
"nop".isBranch() shouldBe false
}
// isStoreReg tests
test("isStoreReg: matches store instructions") {
"sta var".isStoreReg() shouldBe true
"sty var".isStoreReg() shouldBe true
"stx var".isStoreReg() shouldBe true
}
test("isStoreReg: matches store instructions with trailing comments") {
"sta var ; store accumulator".isStoreReg() shouldBe true
"sty myvar ; store Y".isStoreReg() shouldBe true
"stx counter ; store X".isStoreReg() shouldBe true
}
test("isStoreReg: does not match non-store instructions") {
"lda var".isStoreReg() shouldBe false
"ldy var".isStoreReg() shouldBe false
"ldx var".isStoreReg() shouldBe false
"sta".isStoreReg() shouldBe false // no space after
}
// isStoreRegOrZero tests
test("isStoreRegOrZero: matches store and stz") {
"sta var".isStoreRegOrZero() shouldBe true
"sty var".isStoreRegOrZero() shouldBe true
"stx var".isStoreRegOrZero() shouldBe true
"stz var".isStoreRegOrZero() shouldBe true
}
test("isStoreRegOrZero: does not match non-store") {
"lda var".isStoreRegOrZero() shouldBe false
"stz".isStoreRegOrZero() shouldBe false // no space after
}
// isLoadReg tests
test("isLoadReg: matches load instructions") {
"lda var".isLoadReg() shouldBe true
"ldy var".isLoadReg() shouldBe true
"ldx var".isLoadReg() shouldBe true
}
test("isLoadReg: matches load instructions with trailing comments") {
"lda var ; load accumulator".isLoadReg() shouldBe true
"ldy #5 ; load Y".isLoadReg() shouldBe true
"ldx counter ; load X".isLoadReg() shouldBe true
}
test("isLoadReg: does not match non-load instructions") {
"sta var".isLoadReg() shouldBe false
"sty var".isLoadReg() shouldBe false
"stx var".isLoadReg() shouldBe false
"lda".isLoadReg() shouldBe false // no space after
}
// haslabel tests
test("haslabel: detects labels") {
haslabel("mylabel:") shouldBe true
haslabel("loop:") shouldBe true
haslabel("mylabel: lda #1") shouldBe true
haslabel("loop: iny") shouldBe true
}
test("haslabel: detects labels with trailing comments") {
haslabel("mylabel: ; this is a label") shouldBe true
haslabel("loop: lda #1 ; load immediate") shouldBe true
haslabel("skip: ; skip ahead") shouldBe true
}
test("haslabel: does not detect non-labels") {
haslabel(" lda #1") shouldBe false
haslabel(" iny") shouldBe false
haslabel("; comment") shouldBe false
haslabel("") shouldBe false
}
test("haslabel: handles labels with leading whitespace") {
haslabel(" label:") shouldBe true
haslabel("\tlabel:") shouldBe true
}
// keeplabel tests
test("keeplabel: extracts label from line") {
keeplabel("mylabel: lda #1") shouldBe "mylabel:"
keeplabel("loop: iny") shouldBe "loop:"
keeplabel("mylabel:") shouldBe "mylabel:"
}
test("keeplabel: extracts label from line with trailing comment") {
keeplabel("mylabel: lda #1 ; load immediate") shouldBe "mylabel:"
keeplabel("loop: ; just a label") shouldBe "loop:"
keeplabel("skip: rts ; return") shouldBe "skip:"
}
test("keeplabel: returns colon-only for non-label lines with leading whitespace") {
// keeplabel splits on first whitespace and returns first part + ":"
// For " lda #1", first part is empty string, so returns ":"
keeplabel(" lda #1") shouldBe ":"
keeplabel(" iny") shouldBe ":"
}
test("keeplabel: handles tab-separated labels") {
keeplabel("mylabel\tlda #1") shouldBe "mylabel:"
}
// ========================================================================
// KNOWN BUGS - These tests demonstrate current optimizer defects.
// See AsmOptimizer.kt header for the full list.
// ========================================================================
test("float copy init to different destinations should not be removed") {
val lines = mutableListOf(
// Lines 0-6: first float copy
" lda #1", " ldy #0", " sta floatA", " sty floatA+1",
" lda #1", " ldy #0", " jsr floats.copy_float",
// Lines 7-13: second float copy to DIFFERENT destination
" lda #1", " ldy #0", " sta floatB", " sty floatB+1",
" lda #1", " ldy #0", " jsr floats.copy_float"
)
val beforeOpt = lines.toList()
optimize(lines)
lines shouldBe beforeOpt
}
test("variable name containing 'y' should not trigger false positive") {
val lines = mutableListOf(
" ldy #0", " lda (ptr),y", " ora myvar", // 'y' in "myvar" no longer triggers false positive
" ldy #0", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
// The second ldy should be removed since "ora" doesn't modify Y
optimize(lines) shouldBe 1
}
test("actual Y-modifying instructions should still be detected") {
val lines = mutableListOf(
" ldy #0", " lda (ptr),y", " tay", // tay modifies Y, so ldy cannot be removed
" ldy #0", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
// The second ldy should NOT be removed since "tay" modifies Y
optimize(lines) shouldBe 0
}
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
" ldy #0".modifiesYRegister() shouldBe true
" tay".modifiesYRegister() shouldBe true
" iny".modifiesYRegister() shouldBe true
" dey".modifiesYRegister() shouldBe true
" tya".modifiesYRegister() shouldBe true
" phy".modifiesYRegister() shouldBe true
" ply".modifiesYRegister() shouldBe true
" sty var".modifiesYRegister() shouldBe true
" ora myvar".modifiesYRegister() shouldBe false // false positive fixed
" lda #0".modifiesYRegister() shouldBe false
" cpy #0".modifiesYRegister() shouldBe false // compare doesn't modify
" jsr func".modifiesYRegister() shouldBe false
}
test("compiler-prefixed memory-mapped IO variable accesses are not optimized away") {
symbolTable.add(StMemVar("p8b_main.p8v_io_data", DataType.UBYTE, 0xd021u, null, null))
symbolTable.add(StMemVar("p8b_main.p8v_result", DataType.UBYTE, 0x0300u, null, null))
val lines = mutableListOf(
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" rts", " nop"
)
val count = optimize(lines)
count shouldBe 0
lines.size shouldBe 8 // all instructions preserved
}
})
+10 -14
View File
@@ -14,6 +14,9 @@ import prog8.code.source.SourceCode
import prog8.code.target.C64Target
import prog8.codegen.cpu6502.AsmGen6502
import prog8.codegen.cpu6502.VariableAllocator
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
import java.nio.file.Files
import kotlin.io.path.Path
@@ -21,20 +24,13 @@ class TestCodegen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = C64Target()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
return CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.floats(true)
.compilerVersion("99.99")
.memtopAddress(0xffffu)
.build()
}
test("augmented assign on arrays") {
+1 -16
View File
@@ -7,20 +7,5 @@ dependencies {
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
implementation(project(":codeGenIntermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
}
sourceSets {
main {
java {
srcDir(file("${project.projectDir}/src"))
}
resources {
srcDir(file("${project.projectDir}/res"))
}
}
}
// note: there are no unit tests in this module!
@@ -34,6 +34,9 @@ class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
if(!options.quiet)
println("** nothing assembled **")
+4 -34
View File
@@ -7,39 +7,9 @@ dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
}
sourceSets {
main {
java {
srcDir("${project.projectDir}/src")
}
resources {
srcDir("${project.projectDir}/res")
}
}
test {
java {
srcDir("${project.projectDir}/test")
}
}
}
tasks.test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn("cleanTest")
// Show test results.
testLogging {
events("skipped", "failed")
}
testImplementation(testFixtures(project(":codeCore")))
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
+3 -3
View File
@@ -13,8 +13,8 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
</module>
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,5 @@
package prog8.codegen.intermediate
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
@@ -12,14 +11,17 @@ import prog8.intermediate.*
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
fun translate(call: PtFunctionCall): ExpressionCodeResult {
require(call.builtin)
return when(call.name) {
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__long", "sqrt__float" -> funcSqrt(call)
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE, false)
"divmod__uword" -> funcDivmod(call, IRDataType.WORD, false)
"divmod__byte" -> funcDivmod(call, IRDataType.BYTE, true)
"divmod__word" -> funcDivmod(call, IRDataType.WORD, true)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
@@ -30,7 +32,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"msb__long" -> funcMsb(call, true)
"lsb" -> funcLsb(call, false)
"lsb__long" -> funcLsb(call, true)
"memory" -> funcMemory(call)
"lmh" -> funcLmh(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
@@ -57,20 +59,85 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_square_long" -> funcSquare(call, IRDataType.LONG)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"prog8_lib_copylong" -> funcCopyFromPointer1ToPointer2(call, IRDataType.LONG)
"prog8_lib_copyfloat" -> funcCopyFromPointer1ToPointer2(call, IRDataType.FLOAT)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
"swap__byte" -> funcSwap(call)
"swap__word" -> funcSwap(call)
"swap__long" -> funcSwap(call)
"swap__float" -> funcSwap(call)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
"push" -> funcPush(call)
"pushw" -> funcPushW(call)
"pushl" -> funcPushL(call)
"pushf" -> funcPushF(call)
"pop" -> funcPop()
"popw" -> funcPopW()
"popl" -> funcPopL()
"popf" -> funcPopF()
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant ${call.position}")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant ${call.position}")
else -> throw AssemblyError("missing builtinfunc for ${call.name} ${call.position}")
}
}
private fun funcCopyFromPointer1ToPointer2(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
private fun funcPush(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPushW(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPushL(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPushF(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPop(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopW(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopL(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopF(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
}
private fun funcCopyFromPointer1ToPointer2(call: PtFunctionCall, type: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val trSourceAddr = exprGen.translateExpression(call.args[0])
val trTargetAddr = exprGen.translateExpression(call.args[1])
@@ -80,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
IRDataType.LONG -> {
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg)
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg)
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0)
}
}
IRDataType.FLOAT -> {
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, fpReg1=intermediateReg, reg1=trSourceAddr.resultReg)
it += IRInstruction(Opcode.STOREI, type, fpReg1=intermediateReg, reg1=trTargetAddr.resultReg)
it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0)
}
}
else -> throw AssemblyError("invalid type $type")
@@ -96,13 +163,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
private fun funcSquare(call: PtFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
return if(resultType==IRDataType.FLOAT) {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = resultFpReg, fpReg2 = valueTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null)
ExpressionCodeResult(result, resultType, -1, resultFpReg)
}
else {
@@ -112,7 +179,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcCall(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcCall(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
@@ -126,7 +193,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcCallfar(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
@@ -138,7 +205,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
}
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
@@ -160,16 +227,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
}
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val number = call.args[0]
val divident = call.args[1]
val divisionReg: Int
val remainderReg: Int
val divmodOpcode = if(signed) Opcode.SDIVMOD else Opcode.DIVMOD
val divmodrOpcode = if(signed) Opcode.SDIVMODR else Opcode.DIVMODR
if(divident is PtNumber) {
val tr = exprGen.translateExpression(number)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.DIVMOD, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
divisionReg = tr.resultReg
remainderReg = codeGen.registers.next(type)
} else {
@@ -177,19 +246,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, numTr, numTr.resultReg, -1)
val dividentTr = exprGen.translateExpression(divident)
addToResult(result, dividentTr, dividentTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.DIVMODR, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
divisionReg = numTr.resultReg
remainderReg = dividentTr.resultReg
}
// DIVMOD result convention: on value stack, division and remainder on top.
// DIVMOD instruction result convention: on value stack, division and remainder on top.
addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null)
addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null)
result += assignRegisterTo(call.args[2], divisionReg)
result += assignRegisterTo(call.args[3], remainderReg)
// return the 2 result values: division and remainder division in AY, remainder in R15
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=divisionReg)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=divisionReg)
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
}
} else throw AssemblyError("invalid type for DIVMOD")
return ExpressionCodeResult(result, type, -1, -1)
}
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcStringCompare(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
@@ -200,7 +279,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcCmp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcCmp(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = exprGen.translateExpression(call.args[0])
addToResult(result, leftTr, leftTr.resultReg, -1)
@@ -213,7 +292,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
}
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcAbs(call: PtFunctionCall): ExpressionCodeResult {
val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
if(sourceDt.isUnsignedWord)
@@ -257,14 +336,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
else -> throw AssemblyError("weird dt")
}
}
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcSgn(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@@ -272,7 +351,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = tr.resultFpReg)
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg))
}
} else {
addToResult(result, tr, tr.resultReg, -1)
@@ -283,7 +362,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcSqrt(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcSqrt(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
val dt = call.args[0].type
@@ -316,7 +395,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg)
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg))
}
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
@@ -324,7 +403,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcMkword(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(IRDataType.WORD)
if((call.args[0] as? PtNumber)?.number == 0.0) {
@@ -342,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcMklong(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(IRDataType.LONG)
if(call.args.size==2) {
@@ -388,7 +467,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
@@ -425,7 +504,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcMin(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
val type = irType(call.type)
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = exprGen.translateExpression(call.args[0])
@@ -444,7 +523,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcMax(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcMax(call: PtFunctionCall): ExpressionCodeResult {
val type = irType(call.type)
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = exprGen.translateExpression(call.args[0])
@@ -463,74 +542,55 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcSwap(call: PtBuiltinFunctionCall): ExpressionCodeResult {
require(call.args[0].type == call.args[1].type)
val result = mutableListOf<IRCodeChunkBase>()
val dt = irType(call.args[0].type)
val t1 = exprGen.translateExpression(call.args[0])
val t2 = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
addToResult(result, t1, -1, t1.resultFpReg)
addToResult(result, t2, -1, t2.resultFpReg)
addInstr(result, IRInstruction(Opcode.SWAP, dt, fpReg1=t1.resultFpReg, fpReg2=t2.resultFpReg), null)
result += assignFpRegisterTo(call.args[0], t1.resultFpReg)
result += assignFpRegisterTo( call.args[1], t2.resultFpReg)
} else {
addToResult(result, t1, t1.resultReg, -1)
addToResult(result, t2, t2.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SWAP, dt, reg1=t1.resultReg, reg2=t2.resultReg), null)
result += assignRegisterTo(call.args[0], t1.resultReg)
result += assignRegisterTo( call.args[1], t2.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
private fun funcPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val address = (call.args[0] as PtNumber).number.toUInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, dt, address = address)
it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress())
}
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
if(address!=null) {
val tr = exprGen.translateExpression(address)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null)
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
if (call.args[0] is PtNumber) {
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
val address = (call.args[0] as PtNumber).number.toUInt()
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
if(dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
}
addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null)
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
}
addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null)
}
} else {
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
addToResult(result, valueTr, -1, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
if(address!=null) {
val addressTr = exprGen.translateExpression(address)
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null)
}
} else {
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null)
}
}
}
@@ -538,109 +598,77 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
private fun funcPeek(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(dt==IRDataType.FLOAT) {
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
}
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
}
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.next(dt)
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
}
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null)
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(dt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
}
addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null)
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
}
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcPokemon(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val address = call.args[0]
val newValue = call.args[1]
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
}
} else {
val tr = exprGen.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
}
}
}
val newValueTr = if (codeGen.isZero(newValue)) null else exprGen.translateExpression(newValue)
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
}
} else {
val valueTr = exprGen.translateExpression(value)
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
}
}
}
return if(address is PtNumber) {
return if (address is PtNumber) {
val addressNum = address.number.toUInt()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
val addressNum = address.number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null)
}
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
pokeM(result, addressNum, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val addressTr = exprGen.translateExpression(address)
addToResult(result, addressTr, addressTr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
val (baseaddress, offset) = exprGen.getAddressAndOffset(address)
val addrExpr = baseaddress ?: address
val finalOffset = if (baseaddress != null) offset!! else 0
val addressTr = exprGen.translateExpression(addrExpr)
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
}
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
pokeI(result, addressTr.resultReg, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcStructAlloc(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcStructAlloc(call: PtFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
@@ -648,7 +676,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
private fun funcLsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
@@ -661,7 +689,23 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcLmh(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
// TODO this can be more optimal if the argument is a variable or memory address
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
// low byte returned in A, mid in R15, high (bank) in R14
val byteReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null)
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1 = byteReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcLsw(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
@@ -670,7 +714,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
private fun funcMsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
@@ -683,7 +727,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcMsw(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
@@ -692,7 +736,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcRolRor(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val arg = call.args[0]
val vmDt = irType(arg.type)
@@ -710,9 +754,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val memAddr = (arg as? PtMemoryByte)?.address?.asConstInteger()
val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt()
if(memAddr!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr), null)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -754,29 +798,24 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
result += assignRegisterTo(arg, tr.resultReg)
result += codeGen.assignRegisterTo(arg, tr.resultReg)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
private fun funcSetLsbMsb(call: PtFunctionCall, msb: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val target = call.args[0]
val isConstZeroValue = call.args[1].asConstInteger()==0
when(target) {
is PtIdentifier -> {
val offset = if (msb) { if(target.type.isLong) 3 else 1 } else 0
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
}
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset)
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
@@ -784,13 +823,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
}
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset)
}
}
}
@@ -893,28 +926,4 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(false, target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtIrRegister(register, target.type, target.position))
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result
}
private fun assignFpRegisterTo(target: PtExpression, fpRegister: Int): IRCodeChunks {
require(target.type.isFloat)
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(false, target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtIrRegister(fpRegister, DataType.FLOAT, target.position))
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result
}
}
@@ -43,7 +43,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val code = IRCodeChunk(null, null)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), immediateFp = expr.number)
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
}
else {
@@ -54,16 +54,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is PtIdentifier -> {
val code = IRCodeChunk(null, null)
if (expr.type.isPassByValue) {
// Check if this identifier is a constant - if so, use LOAD instead of LOADM to get the constant value directly
val stNode = codeGen.symbolTable.lookup(expr.name)
val isConstant = stNode is StConstant && stNode.value != null
if (isConstant || expr.type.isPassByValue) {
val vmDt = irType(expr.type)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
if (isConstant) {
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name)
} else {
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name)
}
ExpressionCodeResult(code, vmDt, -1, resultFpRegister)
}
else {
val resultRegister = codeGen.registers.next(vmDt)
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
if (isConstant) {
code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
} else {
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
}
ExpressionCodeResult(code, vmDt, resultRegister, -1)
}
} else {
@@ -84,16 +96,53 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtBinaryExpression -> translate(expr)
is PtIfExpression -> translate(expr)
is PtBranchCondExpression -> translate(expr)
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
is PtFunctionCall -> translate(expr)
is PtContainmentCheck -> translate(expr)
is PtPointerDeref -> translate(expr)
is PtConstant -> {
val name = expr.name
val slab = expr.memorySlab
// Check if the constant has a numeric value or is a memory slab reference
if(slab != null) {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StMemorySlabBlockName}.${slab.name}")
ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
} else if(expr.value != null) {
TODO("didn't expect PtConstant to appear here ${expr.position}")
// val vmDt = irType(expr.type)
// val code = IRCodeChunk(null, null)
// val resultRegister = codeGen.registers.next(vmDt)
// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = expr.value!!.toInt())
// ExpressionCodeResult(code, vmDt, resultRegister, -1)
} else {
// Regular constant reference - look up in symbol table
TODO("didn't expect PtConstant to appear here ${expr.position}")
// val vmDt = irType(expr.type)
// val code = IRCodeChunk(null, null)
// val resultRegister = codeGen.registers.next(vmDt)
// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = name)
// ExpressionCodeResult(code, vmDt, resultRegister, -1)
}
}
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
}
}
internal fun getAddressAndOffset(expression: PtExpression): Pair<PtExpression?, Int?> {
// if the expression is of the form address + offset (where offset<=65535), return those components
if(expression is PtBinaryExpression && expression.operator=="+") {
val offset=expression.right as? PtNumber
if(offset!=null && offset.number.toInt()<=65535) {
return expression.left to offset.number.toInt()
}
}
return null to null
}
private fun translate(deref: PtPointerDeref): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
var pointerReg: Int
@@ -162,28 +211,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
result += instructions
if(offset<=0u) {
val irdt = irType(deref.type)
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
// load field with offset
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg, immediate = offset.toInt()), null)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = RegisterNum(resultReg), reg1 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val irdt = irType(deref.type)
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADFIELD, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
@@ -231,10 +267,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
// Use status flags from condition expression when possible, avoiding redundant CMPI #0
val condTr = translateExpression(ifExpr.condition)
addToResult(result, condTr, condTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
val lastInstr = condTr.chunks.lastOrNull()?.instructions?.lastOrNull()
val skipCmpi = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
if (!skipCmpi) {
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) {
@@ -346,8 +387,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
val ptr = codeGen.symbolTable.lookup(identifier.name)
it += if(ptr is StConstant)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
it += if(ptr is StConstant) {
if(ptr.value != null)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value!!.toInt())
else
TODO("LOAD memory() address? ${ptr}")
}
else
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
@@ -398,7 +443,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constAddress = mem.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, address = constAddress.number.toInt()), null)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, address = constAddress.number.toUInt().toAddress()), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
@@ -411,7 +456,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.LOADFIELD, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
@@ -420,7 +465,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(mem.address)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, immediate = 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
@@ -569,7 +614,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val memOffset = index * eltSize
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
@@ -582,7 +627,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += code
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
@@ -620,11 +665,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(resultDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), reg1=pointerReg, immediate = 0), null)
}
else {
resultRegister = codeGen.registers.next(resultDt)
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg), null)
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg, immediate = 0), null)
}
return ExpressionCodeResult(result, resultDt, resultRegister, resultFpRegister)
@@ -639,7 +684,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
"+" -> { }
"-" -> {
if(vmDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null)
else
addInstr(result, IRInstruction(Opcode.NEG, vmDt, reg1 = tr.resultReg), null)
}
@@ -681,7 +726,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg)
it += IRInstruction(Opcode.SGN, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg))
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=actualResultReg2, immediate = 1)
}
}
@@ -703,7 +748,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
@@ -723,7 +768,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
@@ -749,7 +794,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
}
BaseDataType.POINTER -> {
actualResultReg2 = tr.resultReg
@@ -778,7 +823,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
@@ -809,7 +854,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
}
else -> throw AssemblyError("weird cast $valueDt to long ${cast.position}")
}
@@ -818,19 +863,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT)
when(valueDt.base) {
BaseDataType.BOOL, BaseDataType.UBYTE -> {
addInstr(result, IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
addInstr(result, IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
}
BaseDataType.BYTE -> {
addInstr(result, IRInstruction(Opcode.FFROMSB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
addInstr(result, IRInstruction(Opcode.FFROMSB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
}
BaseDataType.UWORD -> {
addInstr(result, IRInstruction(Opcode.FFROMUW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
addInstr(result, IRInstruction(Opcode.FFROMUW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
}
BaseDataType.WORD -> {
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
}
BaseDataType.LONG -> {
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
@@ -860,7 +905,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"%" -> operatorModulo(binExpr, vmDt, binExpr.left.type)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
@@ -880,12 +925,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
if(fcall.builtin)
return codeGen.translateBuiltinFunc(fcall)
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
if(callTarget.scopedNameString in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
return translateStackFunctions(fcall, callTarget)
}
when(callTarget.scopedNameString) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
@@ -920,11 +964,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(arg)
result += tr.chunks
if(paramDt==IRDataType.FLOAT)
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null)))
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null)))
else
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null)))
} else {
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
val assign = PtAssignment(fcall.position)
val target = PtAssignTarget(false, fcall.position)
@@ -941,7 +985,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
callTarget.returns.map {
val returnIrType = irType(it)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null)
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null)
}
}
// create the call
@@ -952,9 +996,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else if(returnRegSpecs.size==1) {
val returnRegSpec = returnRegSpecs.single()
if (fcall.type.isFloat)
ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum)
ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum.value)
else
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1)
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum.value, -1)
} else {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all)
@@ -969,9 +1013,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val paramDt = irType(parameter.type)
val tr = translateExpression(arg)
if(paramDt==IRDataType.FLOAT)
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, parameter.register)))
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), parameter.register)))
else
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), parameter.register)))
result += tr.chunks
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
}
@@ -986,7 +1030,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else {
val returns = callTarget.returns[0]
val returnIrType = irType(returns.type)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), returns.register)
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), returns.register)
}
}
// create the call
@@ -999,12 +1043,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
address = address.address.toAddress(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
} else if(address.constbank!=null) {
IRInstruction(
Opcode.CALLFAR,
address = address.address.toInt(),
address = address.address.toAddress(),
immediate = address.constbank!!.toInt()
)
} else {
@@ -1013,20 +1057,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += tr.chunks
IRInstruction(
Opcode.CALLFARVB,
address = address.address.toInt(),
address = address.address.toAddress(),
reg1 = tr.resultReg
)
}
}
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
var finalReturnRegister = returnRegSpec?.registerNum?.value ?: -1
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
if(statusFlagResult!=null) {
// assign status flag bit to the return value register
finalReturnRegister = returnRegSpec.registerNum
finalReturnRegister = returnRegSpec.registerNum.value
if(finalReturnRegister<0)
finalReturnRegister = codeGen.registers.next(returnRegSpec.dt)
when(statusFlagResult) {
@@ -1109,52 +1153,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return null
}
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
val chunk = mutableListOf<IRCodeChunkBase>()
when(callTarget.scopedNameString) {
"sys.push" -> {
// push byte
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.pushw" -> {
// push word
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
}
"sys.pop" -> {
// pop byte
val popReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
}
"sys.popw" -> {
// pop word
val popReg = codeGen.registers.next(IRDataType.WORD)
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.next(IRDataType.FLOAT)
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 callExtSubWithMultipleReturnValues(
callTarget: StExtSub,
fcall: PtFunctionCall,
@@ -1164,7 +1162,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// return multiple values
val returnRegisters = callTarget.returns.map {
val regnum = codeGen.registers.next(irType(it.type))
FunctionCallArgs.RegSpec(irType(it.type), regnum, it.register)
FunctionCallArgs.RegSpec(irType(it.type), RegisterNum(regnum), it.register)
}
// create the call
val call =
@@ -1175,15 +1173,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
address = address.address.toAddress(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
}
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
}
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}
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum.value}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
}
@@ -1200,29 +1198,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
// TODO can this be done more efficiently? also see operatorLessThan
if(greaterEquals) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.BSTPOS, labelSymbol = other)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
} else {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = other)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 0), other)
}
result += IRCodeChunk(after, null)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
val branch = if(greaterEquals) Opcode.BGES else Opcode.BGTS
val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
} else {
if(binExpr.left.type.isString || binExpr.right.type.isString) {
@@ -1256,29 +1234,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
// TODO can this be done more efficiently? also see operatorGreaterThan
if(lessEquals) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = other)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
} else {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
}
result += IRCodeChunk(after, null)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
val branch = if(lessEquals) Opcode.BLES else Opcode.BLTS
val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
} else {
if(binExpr.left.type.isString || binExpr.right.type.isString) {
@@ -1306,21 +1264,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
val valueReg = codeGen.registers.next(IRDataType.BYTE)
val label = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
it += if (notEquals)
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
else
IRInstruction(Opcode.BSTNE, labelSymbol = label)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
}
result += IRCodeChunk(label, null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0), null)
val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
} else {
if(binExpr.left.type.isString || binExpr.right.type.isString) {
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
@@ -1329,7 +1277,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(rightConst!=null) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
val skipCmpi = rightConst.toInt() == 0 && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
if (!skipCmpi) {
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
}
val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result)
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
} else {
@@ -1346,32 +1298,50 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val labelTrue = codeGen.createLabelName()
val labelDone = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(branchForTrue, labelSymbol = other)
it += IRInstruction(branchForTrue, labelSymbol = labelTrue)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
result += IRCodeChunk(after, null)
result += IRCodeChunk(labelTrue, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
}
result += IRCodeChunk(labelDone, null)
return resultReg
}
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val labelTrue = codeGen.createLabelName()
val labelDone = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(branchForTrue, dt, reg1=reg1, reg2=reg2, labelSymbol = other)
it += IRInstruction(branchForTrue, dt, reg1 = reg1, reg2 = reg2, labelSymbol = labelTrue)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
result += IRCodeChunk(after, null)
result += IRCodeChunk(labelTrue, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
}
result += IRCodeChunk(labelDone, null)
return resultReg
}
private fun loadBooleanStatusAsByte(branchForTrue: Opcode, reg1: Int, result: MutableList<IRCodeChunkBase>): Int {
val labelTrue = codeGen.createLabelName()
val labelDone = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(branchForTrue, IRDataType.BYTE, reg1 = reg1, immediate = 0, labelSymbol = labelTrue)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
}
result += IRCodeChunk(labelTrue, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
}
result += IRCodeChunk(labelDone, null)
return resultReg
}
@@ -1503,20 +1473,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType, dt: DataType): ExpressionCodeResult {
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported ${binExpr.position}"}
val isSigned = dt.isSigned
val result = mutableListOf<IRCodeChunkBase>()
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MOD, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
val opcode = if(isSigned) Opcode.MODS else Opcode.MOD
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MODR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
val opcode = if(isSigned) Opcode.MODSR else Opcode.MODR
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
@@ -1538,9 +1511,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, if(dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg))
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg))
, null)
return ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
@@ -1600,7 +1573,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@@ -1634,21 +1607,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if((binExpr.right as? PtNumber)?.number==1.0) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.DEC, vmDt, fpReg1 = tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.DEC, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
}
else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.SUBR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.SUBR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
}
@@ -1683,27 +1656,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if((binExpr.left as? PtNumber)?.number==1.0) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
}
else if((binExpr.right as? PtNumber)?.number==1.0) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=tr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
}
else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.ADDR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.ADDR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
}
@@ -1793,37 +1766,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
else
resultReg = codeGen.registers.next(fieldVmDt)
if(fieldOffset==0) {
// no offset, can use current pointer directly
result += IRCodeChunk(null, null).also {
it += if (fieldVmDt == IRDataType.FLOAT)
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
}
} else {
// actually have to add an offset
if(fieldOffset<=255) {
if(fieldVmDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, fpReg1 = resultFpReg, reg1 = pointerReg, immediate = fieldOffset), null)
else
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null)
} else {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldOffset)
it += if (fieldVmDt == IRDataType.FLOAT)
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
}
}
}
require(fieldOffset<=65535)
if(fieldVmDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, fpReg1 = RegisterNum(resultFpReg), reg1 = pointerReg, immediate = fieldOffset), null)
else
addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null)
return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg)
}
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, UByte> {
// returns instructions to calculate the pointer address, and the offset into the struct it points to
// so that LOADFIELD and STOREFIELD opcodes can be used instead of having an explicit extra ADD
// so that LOADI and STOREFIELD opcodes can be used instead of having an explicit extra ADD
val result = mutableListOf<IRCodeChunkBase>()
@@ -1838,12 +1791,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
for(deref in targetPointerDeref.chain.dropLast(1)) {
val fieldinfo = struct!!.getField(deref, codeGen.program.memsizer)
struct = fieldinfo.first.subType as StStruct
// get new pointer from field
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
}
result += updatePointerFromField(fieldinfo, pointerReg)
}
}
@@ -1852,10 +1800,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(fieldinfo.second>0u) {
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
result += updatePointerFromField(fieldinfo, pointerReg)
return result to 0u
} else {
return result to fieldinfo.second
@@ -1864,17 +1809,24 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = 0), null)
}
return result to 0u
}
private fun updatePointerFromField(fieldinfo: Pair<DataType, UByte>, pointerReg: Int): IRCodeChunk {
return IRCodeChunk(null, null).also {
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = fieldinfo.second.toInt())
}
}
}
internal fun addInstr(code: MutableList<IRCodeChunkBase>, instr: IRInstruction, label: String?) {
code += IRCodeChunk(label, null).also {
it += instr
}
val chunk = IRCodeChunk(label, null)
chunk.instructions.add(instr)
code += chunk
}
internal fun addToResult(
@@ -1890,7 +1842,7 @@ internal fun addToResult(
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, codeResult.dt, reg1=requiredResultReg, reg2=codeResult.resultReg)
}
if(requiredResultFpReg>=0 && requiredResultFpReg!=codeResult.resultFpReg) {
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = requiredResultFpReg, fpReg2 = codeResult.resultFpReg)
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = RegisterNum(requiredResultFpReg), fpReg2 = RegisterNum(codeResult.resultFpReg))
}
result += codeResult.chunks
}
File diff suppressed because it is too large. Load diff
@@ -3,6 +3,14 @@ package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.intermediate.*
/**
* Represents what value a register currently holds for LOADR forwarding optimization.
*/
private sealed class RegisterContent {
data class Constant(val value: Any) : RegisterContent() // Int or Double
data class Register(val originalReg: Int) : RegisterContent()
}
class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: Boolean) {
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
if(!optimizationsEnabled)
@@ -55,7 +63,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|| cleanupPushPop(chunk1, indexedInstructions)
|| simplifyConstantReturns(chunk1, indexedInstructions)
|| removeNeedlessLoads(chunk1, indexedInstructions)
|| loadfieldsAndStorefields(chunk1, indexedInstructions)
|| removeDeadStores(chunk1, indexedInstructions)
// || removeLoadrForwarding(chunk1, indexedInstructions) // DISABLED - needs debugging
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
} while (changed)
}
@@ -482,7 +491,7 @@ jump p8_label_gen_2
else -> {}
}
fun optimizeImmediateLoadAssociative(replacementOpcode: Opcode) {
fun optimizeImmediateLoad(replacementOpcode: Opcode, isCommutative: Boolean) {
fun getImmediateLoad(reg: Int): Pair<Int, Int>? {
// look if the given register gets an immediate value 1 or 2 istructions back
@@ -501,32 +510,34 @@ jump p8_label_gen_2
}
if(ins.reg1!=null) {
val immediate1 = getImmediateLoad(ins.reg1!!)
if(immediate1!=null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
chunk.instructions.removeAt(immediate1.first)
changed=true
} else {
val immediate2 = getImmediateLoad(ins.reg2!!)
if (immediate2 != null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
chunk.instructions.removeAt(immediate2.first)
changed=true
if (isCommutative) {
val immediate1 = getImmediateLoad(ins.reg1!!)
if (immediate1 != null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
chunk.instructions.removeAt(immediate1.first)
changed = true
return
}
}
val immediate2 = getImmediateLoad(ins.reg2!!)
if (immediate2 != null) {
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
chunk.instructions.removeAt(immediate2.first)
changed = true
}
}
}
// try to use immediate arithmetic instruction if possible
when(ins.opcode) {
Opcode.ADDR -> optimizeImmediateLoadAssociative(Opcode.ADD)
Opcode.MULR -> optimizeImmediateLoadAssociative(Opcode.MUL)
Opcode.MULSR -> optimizeImmediateLoadAssociative(Opcode.MULS)
// Opcode.SUBR -> TODO("ir peephole Subr")
// Opcode.DIVR -> TODO("ir peephole Divr")
// Opcode.DIVSR -> TODO("ir peephole Divsr")
// Opcode.MODR -> TODO("ir peephole Modr")
// Opcode.DIVMODR -> TODO("ir peephole DivModr")
Opcode.ADDR -> optimizeImmediateLoad(Opcode.ADD, true)
Opcode.MULR -> optimizeImmediateLoad(Opcode.MUL, true)
Opcode.MULSR -> optimizeImmediateLoad(Opcode.MULS, true)
Opcode.SUBR -> optimizeImmediateLoad(Opcode.SUB, false)
Opcode.DIVR -> optimizeImmediateLoad(Opcode.DIV, false)
Opcode.DIVSR -> optimizeImmediateLoad(Opcode.DIVS, false)
Opcode.MODR -> optimizeImmediateLoad(Opcode.MOD, false)
// Opcode.DIVMODR - skipped, no immediate DIVMOD variant exists
else -> {}
}
}
@@ -595,21 +606,108 @@ jump p8_label_gen_2
return changed
}
private fun loadfieldsAndStorefields(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.LOADFIELD && ins.immediate==0) {
val loadi = IRInstruction(Opcode.LOADI, ins.type, ins.reg1!!, ins.reg2!!)
chunk.instructions[idx] = loadi
changed = true
private fun removeDeadStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// Detect and remove dead stores: when a register is written but never read before being overwritten.
// Example:
// LOAD r1, #5 <- dead store (r1 overwritten before use)
// LOAD r1, #10
// USE r1
// Track for each register: (index of last write, whether value was read since)
val pendingWrites = mutableMapOf<Int, Pair<Int, Boolean>>() // reg -> (writeIdx, wasRead)
val deadStores = mutableSetOf<Int>()
indexedInstructions.forEach { (idx, ins) ->
if (ins.opcode in OpcodesWithSideEffects) {
// Instruction has side effects, must never be removed as dead store,
// AND it also counts as a "read" of all current pending registers to avoid them being removed
pendingWrites.clear()
return@forEach
}
else if (ins.opcode == Opcode.STOREFIELD && ins.immediate==0) {
val loadi = IRInstruction(Opcode.STOREI, ins.type, ins.reg1!!, ins.reg2!!)
chunk.instructions[idx] = loadi
val formats = instructionFormats.getValue(ins.opcode)
val format = formats[ins.type] ?: formats[null]
// First, check if this instruction READS any registers
if(format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) {
val reg = ins.reg1 ?: ins.fpReg1?.value
if(reg != null) {
val existing = pendingWrites[reg]
if(existing != null) {
pendingWrites[reg] = existing.first to true
}
}
}
if(format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) {
val reg = ins.reg2 ?: ins.fpReg2?.value
if(reg != null) {
val existing = pendingWrites[reg]
if(existing != null) {
pendingWrites[reg] = existing.first to true
}
}
}
if(format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) {
val reg = ins.reg3
if(reg != null) {
val existing = pendingWrites[reg]
if(existing != null) {
pendingWrites[reg] = existing.first to true
}
}
}
// Then, check if this instruction WRITES to any registers
if(format?.reg1 == OperandDirection.WRITE || format?.reg1 == OperandDirection.READWRITE) {
val reg = ins.reg1 ?: ins.fpReg1?.value
if(reg != null) {
// Check if previous write to this reg was dead (never read before this overwrite)
val existing = pendingWrites[reg]
if(existing != null && !existing.second) {
deadStores.add(existing.first)
}
// Record this new write as pending (not yet read)
pendingWrites[reg] = idx to false
}
}
}
// Any remaining pending writes that were never read are also dead
// (unless they're the final value needed - but we can't know that here)
// Actually, we should NOT remove these because they might be the final value
// Remove dead stores (in reverse order to preserve indices)
var changed = false
deadStores.sortedDescending().forEach { idx ->
if(idx < chunk.instructions.size) {
chunk.instructions.removeAt(idx)
changed = true
}
}
return changed
}
}
private fun isRegisterRead(ins: IRInstruction, reg: Int): Boolean {
// Check if the instruction reads from the given register
val formats = instructionFormats.getValue(ins.opcode)
val format = formats[ins.type] ?: formats[null]
val reg1Read = (format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) && (ins.reg1 == reg || ins.fpReg1?.value == reg)
val reg2Read = (format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) && (ins.reg2 == reg || ins.fpReg2?.value == reg)
val reg3Read = (format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) && (ins.reg3 == reg)
return reg1Read || reg2Read || reg3Read
}
//private fun removeLoadrForwarding(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// Forward LOADR instructions to their original source.
// Example:
// LOAD r1, #5
// LOADR r2, r1 -> LOAD r2, #5
// LOADR r3, r2 -> LOAD r3, #5
// todo: This needs more careful implementation considering:
// - Cross-chunk register usage
// - Function call side effects
// - Proper invalidation of register tracking
//}
}
@@ -40,10 +40,10 @@ class IRUnusedCodeRemover(
}
irprog.st.allVariables().forEach { variable ->
val initValue = variable.onetimeInitializationArrayValue
if(!initValue.isNullOrEmpty()) {
if(initValue.any {
it.addressOfSymbol?.startsWith(blockLabel)==true
val initValue = variable.initializationValue
if(initValue is IRVariableInitializer.Array) {
if(initValue.elements.any {
it is IRStSymbolicReference.Symbol && it.name.startsWith(blockLabel)
})
return // symbol occurs in an initializer value (address-of this symbol)_
}
@@ -85,7 +85,7 @@ class IRUnusedCodeRemover(
instructions.remove(ins)
}
}
else if(ins.fpReg1!=0) {
else if(ins.fpReg1!=null) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
@@ -106,12 +106,16 @@ class IRUnusedCodeRemover(
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!block.options.ignoreUnused) {
errors.info("unused subroutine '${sub.label}'", sub.position)
// Don't remove subroutines with @shared variables
val hasVariables = irprog.st.allVariables().any { it.name.startsWith(sub.label + ".") }
if(!hasVariables) {
if(!block.options.ignoreUnused) {
errors.info("unused subroutine '${sub.label}'", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
numRemoved++
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
numRemoved++
}
}
}
@@ -200,10 +204,11 @@ class IRUnusedCodeRemover(
irprog.st.allVariables()
.filter { !it.uninitialized }
.forEach {
it.onetimeInitializationArrayValue?.let { array ->
array.forEach {elt ->
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
reachable.add(irprog.getChunkWithLabel(elt.addressOfSymbol!!))
val initValue = it.initializationValue
if(initValue is IRVariableInitializer.Array) {
initValue.elements.forEach {elt ->
if(elt is IRStSymbolicReference.Symbol && irprog.st.lookup(elt.name)==null)
reachable.add(irprog.getChunkWithLabel(elt.name))
}
}
}
@@ -246,10 +251,11 @@ class IRUnusedCodeRemover(
irprog.st.allVariables()
.filter { !it.uninitialized }
.forEach {
it.onetimeInitializationArrayValue?.let { array ->
array.forEach {elt ->
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
if(it.initializationValue is IRVariableInitializer.Array) {
val initValue = it.initializationValue as IRVariableInitializer.Array
initValue.elements.forEach {elt ->
if(elt is IRStSymbolicReference.Symbol && irprog.st.lookup(elt.name)==null)
linkedChunks += irprog.getChunkWithLabel(elt.name)
}
}
}
@@ -5,15 +5,33 @@ import prog8.code.ast.*
internal fun makeAllNodenamesScoped(program: PtProgram) {
val renames = mutableListOf<Pair<PtNamedNode, String>>()
val constRenames = mutableListOf<Pair<PtConstant, String>>()
fun computeScopedName(node: PtNode): String {
val name = if(node is PtNamedNode) node.name else (node as? PtConstant)?.name ?: return ""
var parent = node.parent
while(true) {
when(parent) {
is PtNamedNode -> return "${parent.scopedName}.$name"
is PtConstant -> return "${parent.name}.$name"
is PtProgram -> return name
else -> {}
}
parent = parent.parent
}
}
fun recurse(node: PtNode) {
node.children.forEach {
if(it is PtNamedNode)
renames.add(it to it.scopedName)
when(it) {
is PtNamedNode -> renames.add(it to it.scopedName)
is PtConstant -> constRenames.add(it to computeScopedName(it))
else -> {}
}
recurse(it)
}
}
recurse(program)
renames.forEach { it.first.name = it.second }
constRenames.forEach { it.first.name = it.second }
}
internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
@@ -22,12 +40,14 @@ internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
fun moveToBlock(block: PtBlock, parent: PtSub, asmsub: PtAsmSub) {
block.add(asmsub)
parent.children.remove(asmsub)
parent.removeChild(asmsub)
}
fun moveToBlock(block: PtBlock, parent: PtSub, sub: PtSub) {
// First recursively move nested subroutines
sub.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, sub, subsub) }
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, sub, asmsubsub) }
movedSubs += Pair(block, sub)
removedSubs += Pair(parent, sub)
}
@@ -42,6 +62,6 @@ internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
}
}
removedSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
removedSubs.forEach { (parent, sub) -> parent.removeChild(sub) }
movedSubs.forEach { (block, sub) -> block.add(sub) }
}
@@ -2,6 +2,7 @@ package prog8.codegen.intermediate
import prog8.code.core.AssemblyError
import prog8.intermediate.IRDataType
import prog8.intermediate.RegisterNum
internal class RegisterPool {
// everything from 99000 onwards is reserved for special purposes:
@@ -10,17 +11,17 @@ internal class RegisterPool {
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
private var nextRegister: Int=1
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
private val registerTypes: MutableMap<RegisterNum, IRDataType> = mutableMapOf()
fun getTypes(): Map<Int, IRDataType> = registerTypes
fun getTypes(): Map<RegisterNum, IRDataType> = registerTypes
init {
for(i in 99000..99099)
registerTypes[i] = IRDataType.WORD
registerTypes[RegisterNum(i)] = IRDataType.WORD
for(i in 99100..99199)
registerTypes[i] = IRDataType.BYTE
registerTypes[RegisterNum(i)] = IRDataType.BYTE
for(i in 99200..99299)
registerTypes[i] = IRDataType.LONG
registerTypes[RegisterNum(i)] = IRDataType.LONG
}
fun next(type: IRDataType): Int {
@@ -28,7 +29,7 @@ internal class RegisterPool {
throw AssemblyError("register pool depleted")
val result = nextRegister
nextRegister++
registerTypes[result] = type
registerTypes[RegisterNum(result)] = type
return result
}
}
@@ -4,22 +4,163 @@ import prog8.code.*
import prog8.code.core.DataType
import prog8.intermediate.*
/**
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
* Groups all conversion functions together for better organization.
*/
private object StToIrConverter {
fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
return when(elt) {
is StArrayElement.BoolValue -> IRStSymbolicReference.BoolValue(elt.value)
is StArrayElement.Number -> IRStSymbolicReference.Numeric(elt.value)
// Memory slabs and address-of are both stored as symbol references in the IR
is StArrayElement.MemorySlab -> IRStSymbolicReference.Symbol("$StMemorySlabBlockName.${elt.name}")
is StArrayElement.AddressOf -> IRStSymbolicReference.Symbol(elt.symbol)
is StArrayElement.StructInstance -> {
// Struct instances in arrays are stored as symbol references to the struct instance block
val symbol = StStructInstanceBlockName + "." + (if(elt.uninitialized) elt.name else elt.name)
IRStSymbolicReference.Symbol(symbol)
}
}
}
private fun makeInitializer(
variable: StStaticVariable,
array: List<StArrayElement>?
): IRVariableInitializer? {
if(array==null && variable.initializationNumericValue==null && variable.initializationStringValue==null)
return null
return when {
variable.initializationStringValue != null -> {
val str = variable.initializationStringValue!!
IRVariableInitializer.Str(str.first, str.second)
}
variable.initializationNumericValue != null -> {
IRVariableInitializer.Numeric(variable.initializationNumericValue!!)
}
array != null -> {
val scopedArray = array.map {
when(it) {
is StArrayElement.AddressOf -> {
val target = variable.lookup(it.symbol) ?:
throw NoSuchElementException("can't find variable ${it.symbol}")
IRStSymbolicReference.Symbol(target.scopedNameString)
}
else -> convertArrayElt(it)
}
}
IRVariableInitializer.Array(scopedArray)
}
else -> null
}
}
fun convert(variable: StStaticVariable): IRStStaticVariable {
val initValue = if('.' in variable.name) {
when {
variable.initializationStringValue != null -> IRVariableInitializer.Str(variable.initializationStringValue!!.first, variable.initializationStringValue!!.second)
variable.initializationNumericValue != null -> IRVariableInitializer.Numeric(variable.initializationNumericValue!!)
variable.initializationArrayValue != null -> IRVariableInitializer.Array(variable.initializationArrayValue!!.map { convertArrayElt(it) })
else -> null
}
} else {
makeInitializer(variable, variable.initializationArrayValue)
}
val name = if('.' in variable.name) variable.name else variable.scopedNameString
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty)
}
fun convert(variable: StMemVar): IRStMemVar {
if('.' in variable.name) {
return IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
)
} else {
val scopedName = try {
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
}
}
fun convert(constant: StConstant): IRStConstant {
val scopedName = if('.' in constant.name) {
constant.name
} else {
try {
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
}
if(constant.value != null)
return IRStConstant(scopedName, constant.dt, constant.value)
else if(constant.memorySlab != null) {
// For memory() constants, store reference to slab name
// The actual address will be resolved during code generation
val slabName = constant.memorySlab!!.name
return IRStConstant(scopedName, constant.dt, null, slabName)
}
else
TODO("constant without value or memory slab: $constant")
}
fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
}
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
val st = IRSymbolTable()
if (sourceSt != null) {
sourceSt.flat.forEach {
when(it.value.type) {
StNodeType.STATICVAR -> st.add(convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
StNodeType.STATICVAR -> st.add(StToIrConverter.convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(StToIrConverter.convert(it.value as StMemVar))
StNodeType.CONSTANT -> {
val constant = it.value as StConstant
// If the constant has a memory() slab, add the slab to the IR symbol table first
constant.memorySlab?.let { slab ->
val converted = StToIrConverter.convert(slab)
st.add(converted)
}
st.add(StToIrConverter.convert(constant))
}
StNodeType.MEMORYSLAB -> {
val slab = it.value as StMemorySlab
val converted = StToIrConverter.convert(slab)
st.add(converted)
}
StNodeType.STRUCTINSTANCE -> {
val instance = it.value as StStructInstance
val struct = sourceSt.lookup(instance.structName) as StStruct
st.add(convert(instance, struct.fields))
st.add(StToIrConverter.convert(instance, struct.fields))
}
StNodeType.STRUCT -> st.add(convert(it.value as StStruct))
StNodeType.STRUCT -> st.add(StToIrConverter.convert(it.value as StStruct))
else -> { }
}
}
@@ -27,12 +168,12 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
st.validate()
st.allVariables().forEach { variable ->
variable.onetimeInitializationArrayValue?.let {
it.forEach { arrayElt ->
val addrOfSymbol = arrayElt.addressOfSymbol
if (addrOfSymbol != null) {
require(addrOfSymbol.contains('.')) {
"pointer var in array should be properly scoped: $addrOfSymbol in ${variable.name}"
if(variable.initializationValue is IRVariableInitializer.Array) {
val initValue = variable.initializationValue as IRVariableInitializer.Array
initValue.elements.forEach { arrayElt ->
if (arrayElt is IRStSymbolicReference.Symbol) {
require(arrayElt.name.contains('.')) {
"pointer var in array should be properly scoped: ${arrayElt.name} in ${variable.name}"
}
}
}
@@ -41,115 +182,3 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
}
return st
}
private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
return if (elt.boolean != null)
IRStArrayElement(elt.boolean, null, null)
else if(elt.number!=null)
IRStArrayElement(null, elt.number, null)
else {
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
IRStArrayElement(null, null, symbol)
}
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
if('.' in variable.name) {
return IRStStaticVariable(variable.name,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align,
variable.dirty)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
return null
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?:
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
val scopedName = variable.scopedNameString
return IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align,
variable.dirty
)
}
}
private fun convert(variable: StMemVar): IRStMemVar {
if('.' in variable.name) {
return IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
)
} else {
val scopedName = try {
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
}
}
private fun convert(constant: StConstant): IRStConstant {
val scopedName = if('.' in constant.name) {
constant.name
} else {
try {
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
}
return IRStConstant(scopedName, constant.dt, constant.value)
}
private fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
@@ -32,6 +32,15 @@ internal class VmAssemblyProgram(
internal val irProgram: IRProgram
): IAssemblyProgram {
override val irInstructionCount: Int
get() = irProgram.countCodeElements().first
override val irChunkCount: Int
get() = irProgram.countCodeElements().second
override val irRegisterCount: Int
get() = irProgram.countUsedRegisters()
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
// the VM reads the IR file from disk.
IRFileWriter(irProgram, null).write()
-91
View File
@@ -1,91 +0,0 @@
import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
val infos = mutableListOf<String>()
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in errors)
errors.add(text)
}
override fun warn(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
infos.add(text)
}
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
infos.clear()
}
}
+103 -42
View File
@@ -4,6 +4,7 @@ import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.intermediate.IRPeepholeOptimizer
import prog8.intermediate.*
import prog8tests.helpers.ErrorReporterForTests
class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
@@ -13,20 +14,12 @@ class TestIRPeepholeOpt: FunSpec({
chunks.forEach { sub += it }
block += sub
val target = VMTarget()
val options = CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
val options = CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.noSysInit(true)
.compilerVersion("99.99")
.build()
val prog = IRProgram("test", IRSymbolTable(), options, target)
prog.addBlock(block)
prog.linkChunks()
@@ -152,28 +145,31 @@ class TestIRPeepholeOpt: FunSpec({
}
test("remove useless div/mul, add/sub") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 2),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 2),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 2),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 2),
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=1, immediate = 1),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=2, immediate = 1),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=3, immediate = 1),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=4, immediate = 1),
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=5, immediate = 2),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=6, immediate = 2),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=7, immediate = 2),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=8, immediate = 2),
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=9, immediate = 0),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=10, immediate = 0)
))
irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
// First 4 are removed (div/mul/mod by 1), last 2 become no-ops (add/sub 0)
irProg.chunks().single().instructions.size shouldBe 4
}
test("replace add/sub 1 by inc/dec") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 1),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=2, immediate = 1)
))
irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg, false)
@@ -185,30 +181,51 @@ class TestIRPeepholeOpt: FunSpec({
}
test("remove useless and/or/xor") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 255),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 0),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 200),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 60000),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=4, immediate = 255),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=5, immediate = 65535),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=6, immediate = 2147483647),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=7, immediate = -1),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=8, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=9, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=10, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=11, immediate = 2147483647),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=12, immediate = -1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=13, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=14, immediate = 200),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=15, immediate = 60000),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=16, immediate = 1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=17, immediate = 1)
))
irProg.chunks().single().instructions.size shouldBe 8
irProg.chunks().single().instructions.size shouldBe 17
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 4
// After optimization:
// - AND #0 (3x) -> LOAD #0
// - AND #max (2x) -> removed
// - AND #other (3x) -> stays
// - OR #0 (1x) -> removed
// - OR #max (3x) -> LOAD #max
// - XOR #0 (1x) -> removed
// - other (3x) -> stays
// Total: 17 - 5 (removed) = 12, with 6 LOAD instructions
irProg.chunks().single().instructions.size shouldBe 12
irProg.chunks().single().instructions.count { it.opcode == Opcode.LOAD } shouldBe 6
}
test("replace and/or/xor by constant number") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=42, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=42, immediate = -1)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=4, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=5, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=6, immediate = -1)
))
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg, false)
@@ -228,4 +245,48 @@ class TestIRPeepholeOpt: FunSpec({
instr[4].immediate shouldBe 65535
instr[5].immediate shouldBe -1
}
test("replace register arithmetic by immediate operations") {
// Test the new optimization: LOAD + SUBR/DIVR/MODR → immediate operation
// Pattern: LOAD r1, #const followed by SUBR r2, r1 → SUB r2, #const
// SUBR optimization
val subrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate = 5),
IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=2, reg2=1)
))
subrProg.chunks().single().instructions.size shouldBe 2
val opt1 = IRPeepholeOptimizer(subrProg, false)
opt1.optimize(true, ErrorReporterForTests())
val subInstr = subrProg.chunks().single().instructions
subInstr.size shouldBe 1
subInstr[0].opcode shouldBe Opcode.SUB
subInstr[0].immediate shouldBe 5
// DIVR optimization
val divrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=10, immediate = 100),
IRInstruction(Opcode.DIVR, IRDataType.WORD, reg1=20, reg2=10)
))
divrProg.chunks().single().instructions.size shouldBe 2
val opt2 = IRPeepholeOptimizer(divrProg, false)
opt2.optimize(true, ErrorReporterForTests())
val divInstr = divrProg.chunks().single().instructions
divInstr.size shouldBe 1
divInstr[0].opcode shouldBe Opcode.DIV
divInstr[0].immediate shouldBe 100
// MODR optimization
val modrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=30, immediate = 7),
IRInstruction(Opcode.MODR, IRDataType.BYTE, reg1=40, reg2=30)
))
modrProg.chunks().single().instructions.size shouldBe 2
val opt3 = IRPeepholeOptimizer(modrProg, false)
opt3.optimize(true, ErrorReporterForTests())
val modInstr = modrProg.chunks().single().instructions
modInstr.size shouldBe 1
modInstr[0].opcode shouldBe Opcode.MOD
modInstr[0].immediate shouldBe 7
}
})
+82 -17
View File
@@ -10,25 +10,21 @@ import prog8.codegen.vm.VmAssemblyProgram
import prog8.codegen.vm.VmCodeGen
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.intermediate.toAddress
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
class TestVmCodeGen: FunSpec({
fun getTestOptions(): CompilationOptions {
val target = VMTarget()
return CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
return CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(ZeropageType.DONTUSE)
.floats(true)
.compilerVersion("99.99")
.build()
}
test("augmented assigns") {
@@ -148,6 +144,75 @@ class TestVmCodeGen: FunSpec({
irChunks.size shouldBe 1
}
test("augmented assigns with PtMemoryByte target - all register opcodes") {
// Tests the fix for using ADDR/SUBR/ANDR/ORR/XORR (register-to-register)
// instead of ADD/SUB/AND/OR/XOR (register+immediate) when the target is a
// PtMemoryByte with a non-constant address and the operand is a variable.
//
// Pattern: @(ptr+offset) = @(ptr+offset) + val i.e. ptr[offset] += val
// Non-zero offsets are used to avoid potential optimization of the address expression.
val codegen = VmCodeGen(false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable("ptr", DataType.pointer(BaseDataType.UBYTE), ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
sub.add(PtVariable("val", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
fun makeMemByteAssign(op: String, offset: Int): PtAugmentedAssign {
// Use non-zero offset to avoid optimization
val addrExpr = PtBinaryExpression("+", DataType.UWORD, Position.DUMMY)
addrExpr.add(PtIdentifier("main.start.ptr", DataType.pointer(BaseDataType.UBYTE), Position.DUMMY))
addrExpr.add(PtNumber(BaseDataType.UWORD, offset.toDouble(), Position.DUMMY))
val memByte = PtMemoryByte(Position.DUMMY)
memByte.add(addrExpr)
val target = PtAssignTarget(false, Position.DUMMY)
target.add(memByte)
val assign = PtAugmentedAssign(op, Position.DUMMY)
assign.add(target)
assign.add(PtIdentifier("main.start.val", DataType.UBYTE, Position.DUMMY))
return assign
}
sub.add(makeMemByteAssign("+=", 3))
sub.add(makeMemByteAssign("-=", 5))
sub.add(makeMemByteAssign("|=", 7))
sub.add(makeMemByteAssign("&=", 11))
sub.add(makeMemByteAssign("^=", 13))
block.add(sub)
program.add(block)
val options = getTestOptions()
val st = SymbolTableMaker(program, options).make()
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
val instructions = irChunks[0].instructions
// Verify correct register-to-register opcodes are used (not immediate variants)
val addrCount = instructions.count { it.opcode == Opcode.ADDR }
val subrCount = instructions.count { it.opcode == Opcode.SUBR }
val orrCount = instructions.count { it.opcode == Opcode.ORR }
val andrCount = instructions.count { it.opcode == Opcode.ANDR }
val xorrCount = instructions.count { it.opcode == Opcode.XORR }
addrCount shouldBe 1
subrCount shouldBe 1
orrCount shouldBe 1
andrCount shouldBe 1
xorrCount shouldBe 1
// Verify that the immediate-only variants are NOT used with reg2
val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.reg2 != null }
val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.reg2 != null }
val wrongOr = instructions.any { it.opcode == Opcode.OR && it.reg2 != null }
val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.reg2 != null }
val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.reg2 != null }
(wrongAdd || wrongSub || wrongOr || wrongAnd || wrongXor) shouldBe false
}
test("float comparison expressions against zero") {
//main {
// sub start() {
@@ -554,7 +619,7 @@ class TestVmCodeGen: FunSpec({
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(), emptyList(), Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
val call = PtFunctionCall("main.routine", false, false, emptyArray(), Position.DUMMY)
sub.add(call)
block.add(sub)
program.add(block)
@@ -568,6 +633,6 @@ class TestVmCodeGen: FunSpec({
irChunks[0].instructions.size shouldBe 1
val callInstr = irChunks[0].instructions[0]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
callInstr.address shouldBe 0x5000u.toAddress()
}
})
})
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+1 -16
View File
@@ -5,20 +5,5 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":compilerAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
}
sourceSets {
main {
java {
srcDir("${project.projectDir}/src")
}
resources {
srcDir("${project.projectDir}/res")
}
}
}
// note: there are no unit tests in this module!
@@ -262,6 +262,8 @@ class ConstExprEvaluator {
"round" -> round(args[0].number)
"floor" -> floor(args[0].number)
"ceil" -> ceil(args[0].number)
"sqrt" -> sqrt(args[0].number)
"abs" -> abs(args[0].number)
"minf", "min" -> min(args[0].number, args[1].number)
"maxf", "max" -> max(args[0].number, args[1].number)
"clampf", "clamp" -> {
@@ -373,7 +375,7 @@ class ConstExprEvaluator {
}
"isspace" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
}
"isprint" -> {
val char = args[0].number.toInt()
@@ -1,17 +1,13 @@
package prog8.optimizer
import prog8.ast.FatalAstException
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.BaseDataType
import prog8.code.core.IErrorReporter
import prog8.code.core.isInteger
import prog8.ast.walk.*
import prog8.code.core.*
import kotlin.math.floor
@@ -19,22 +15,22 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
private val evaluator = ConstExprEvaluator()
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
override fun after(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
val constAddr = addressOf.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(addressOf, constAddr, parent))
return listOf(AstReplaceNode(addressOf, constAddr, parent))
}
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<AstModification> {
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) {
if(addrOf.identifier?.inferType(program)?.isBytes==true)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier!!, parent))
return listOf(AstReplaceNode(memread, addrOf.identifier!!, parent))
}
return noModifications
}
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<AstModification> {
if(numLiteral.type==BaseDataType.LONG) {
// see if LONG values may be reduced to something smaller
@@ -42,7 +38,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(smaller.type!=BaseDataType.LONG) {
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
// do NOT reduce the type if the target of the assignment is a long
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
return listOf(AstReplaceNode(numLiteral, smaller, parent))
}
}
}
@@ -52,23 +48,35 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(iDt.isKnown && !iDt.isBool && !(iDt issimpletype numLiteral.type)) {
val casted = numLiteral.cast(iDt.getOrUndef().base, true)
if(casted.isValid) {
return listOf(IAstModification.ReplaceNode(numLiteral, casted.valueOrZero(), parent))
return listOf(AstReplaceNode(numLiteral, casted.valueOrZero(), parent))
}
}
}
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
override fun after(containment: ContainmentCheck, parent: Node): Iterable<AstModification> {
val result = containment.constValue(program)
if(result!=null)
return listOf(IAstModification.ReplaceNode(containment, result, parent))
return listOf(AstReplaceNode(containment, result, parent))
return noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
override fun after(expr: PrefixExpression, parent: Node): Iterable<AstModification> {
val constValue = expr.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(expr, constValue, parent))
return listOf(AstReplaceNode(expr, constValue, parent))
}
private fun getPointerConstValue(expr: Expression): NumericLiteral? {
if (expr.inferType(program).isPointer) {
if (expr is IdentifierReference) {
val target = expr.definingScope.lookup(expr.nameInSource) as? VarDecl
if (target?.type == VarDeclType.CONST) {
return target.value?.constValue(program)
}
}
}
return expr.constValue(program)
}
/*
@@ -88,10 +96,41 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
override fun after(expr: BinaryExpression, parent: Node): Iterable<AstModification> {
if(expr.operator==".")
return noModifications
val modifications = mutableListOf<IAstModification>()
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
// pointer arithmetic: ptr + offset, ptr - offset
if (leftDt.isPointer && (expr.operator == "+" || expr.operator == "-") && !rightDt.isPointer) {
val leftPtrConst = getPointerConstValue(expr.left)
val rightOffsetConst = expr.right.constValue(program)
if (leftPtrConst != null && rightOffsetConst != null) {
val ptrDt = leftDt.getOrUndef()
val subDt = ptrDt.sub
val scale = if(subDt is BaseDataType) program.memsizer.memorySize(subDt) else ptrDt.size(program.memsizer)
val offset = rightOffsetConst.number.toInt() * scale
val result = if (expr.operator == "+") leftPtrConst.number + offset else leftPtrConst.number - offset
return listOf(AstReplaceNode(expr, NumericLiteral(BaseDataType.UWORD, result, expr.position), parent))
}
}
// commutative pointer arithmetic: offset + ptr
if (rightDt.isPointer && expr.operator == "+" && !leftDt.isPointer) {
val rightPtrConst = getPointerConstValue(expr.right)
val leftOffsetConst = expr.left.constValue(program)
if (rightPtrConst != null && leftOffsetConst != null) {
val ptrDt = rightDt.getOrUndef()
val subDt = ptrDt.sub
val scale = if(subDt is BaseDataType) program.memsizer.memorySize(subDt) else ptrDt.size(program.memsizer)
val offset = leftOffsetConst.number.toInt() * scale
val result = rightPtrConst.number + offset
return listOf(AstReplaceNode(expr, NumericLiteral(BaseDataType.UWORD, result, expr.position), parent))
}
}
val modifications = mutableListOf<AstModification>()
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -108,7 +147,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
leftString.encoding)
}
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
return listOf(AstReplaceNode(expr, concatStr, parent))
}
else if (expr.operator=="*" && expr.left is StringLiteral) {
if (rightconst != null) {
@@ -122,7 +161,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
errors.err("can only multiply string by integer constant", rightconst.position)
else {
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
return listOf(AstReplaceNode(expr, newStr, parent))
}
}
// other errors are reported later
@@ -148,7 +187,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
part.value.forEach { tmp += it.copy() }
}
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
return listOf(AstReplaceNode(expr, newArray, parent))
}
} else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
@@ -173,16 +212,16 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// X + С1 == C2 --> X == C2 - C1
val newRightConst = NumericLiteral(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
AstReplaceNode(leftExpr, leftExpr.left, expr),
AstReplaceNode(expr.right, newRightConst, expr)
)
}
"-" -> {
// X - С1 == C2 --> X == C2 + C1
val newRightConst = NumericLiteral(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
AstReplaceNode(leftExpr, leftExpr.left, expr),
AstReplaceNode(expr.right, newRightConst, expr)
)
}
}
@@ -216,7 +255,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// if in a chained comparison, that one has to be desugared first though.
if(leftconst != null && rightconst != null) {
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
modifications += IAstModification.ReplaceNode(expr, result, parent)
modifications += AstReplaceNode(expr, result, parent)
}
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
@@ -224,13 +263,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// X - -1 ---> X + 1
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))
return listOf(AstReplaceNode(expr, plusExpr, parent))
}
else if (expr.operator == "+") {
// X + -1 ---> X - 1
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))
return listOf(AstReplaceNode(expr, plusExpr, parent))
}
}
@@ -248,7 +287,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val operator = if(leftBinExpr.operator=="+") expr.operator else if(expr.operator=="-") "+" else "-"
val constants = BinaryExpression(c2, operator, rightconst, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, leftBinExpr.operator, constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return listOf(AstReplaceNode(expr, newExpr, parent))
}
}
}
@@ -259,13 +298,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// (X * C2) * rightConst --> X * (rightConst*C2)
val constants = BinaryExpression(rightconst, "*", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return listOf(AstReplaceNode(expr, newExpr, parent))
} else if (leftBinExpr.operator=="/") {
if(expr.inferType(program) issimpletype BaseDataType.FLOAT) {
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return listOf(AstReplaceNode(expr, newExpr, parent))
}
}
}
@@ -277,7 +316,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return listOf(AstReplaceNode(expr, newExpr, parent))
}
}
}
@@ -292,7 +331,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
modifications += AstReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
@@ -301,7 +340,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
modifications += AstReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
@@ -312,7 +351,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(xDt==yDt) {
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
modifications += AstReplaceNode(expr, newExpr, parent)
}
}
}
@@ -322,7 +361,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return modifications
}
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
override fun after(array: ArrayLiteral, parent: Node): Iterable<AstModification> {
// because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype.
@@ -335,26 +374,26 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(vardeclType!=null) {
val newArray = array.cast(vardeclType)
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
return listOf(AstReplaceNode(array, newArray, parent))
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.getOrUndef())
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
return listOf(AstReplaceNode(array, newArray, parent))
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<AstModification> {
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))
return listOf(AstReplaceNode(arrayIndexedExpression, const, parent))
val constIndex = arrayIndexedExpression.indexer.constIndex()
if (constIndex != null) {
@@ -365,7 +404,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(array!=null) {
val value = array.value[constIndex].constValue(program)
if(value!=null) {
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
return listOf(AstReplaceNode(arrayIndexedExpression, value, parent))
}
}
}
@@ -377,20 +416,54 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
val constValues = functionCallExpr.constValues(program)
if (constValues != null) {
if (constValues.size == 1) {
return listOf(AstReplaceNode(functionCallExpr, constValues[0], parent))
} else {
// Multi-value return
val container = parent.parent as? IStatementContainer ?: return noModifications
if (parent is Assignment && parent.target.multi?.size == constValues.size) {
val mods = mutableListOf<AstModification>()
for (i in constValues.indices.reversed()) {
val assign = Assignment(parent.target.multi!![i], constValues[i], AssignmentOrigin.USERCODE, parent.position)
mods.add(AstInsert.after(parent, assign, container))
}
mods.add(AstRemove(parent, container))
return mods
} else if (parent is VarDecl && parent.names.size == constValues.size) {
val mods = mutableListOf<AstModification>()
for (i in constValues.indices.reversed()) {
val newDecl = VarDecl.builder(DataType.forDt(constValues[i].type), parent.position)
.copyFrom(parent)
.names(parent.names[i])
.type(VarDeclType.CONST)
.value(constValues[i])
.hasExplicitInitializer(true)
.build()
newDecl.allowInitializeWithZero = false
mods.add(AstInsert.after(parent, newDecl, container))
}
mods.add(AstRemove(parent, container))
return mods
}
}
}
val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
listOf(AstReplaceNode(functionCallExpr, constvalue, parent))
else {
val const2 = evaluator.evaluate(functionCallExpr, program)
if(const2!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
listOf(AstReplaceNode(functionCallExpr, const2, parent))
else
noModifications
}
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
override fun after(forLoop: ForLoop, parent: Node): Iterable<AstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: BaseDataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
val fromCast = rangeFrom.cast(targetDt, true)
val toCast = rangeTo.cast(targetDt, true)
@@ -427,7 +500,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// attempt to translate the iterable into ubyte values
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
BaseDataType.BYTE -> {
@@ -435,7 +508,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
BaseDataType.UWORD -> {
@@ -443,7 +516,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
BaseDataType.WORD -> {
@@ -451,7 +524,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
@@ -460,7 +533,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
if (decl.type == VarDeclType.CONST && decl.datatype.isPointer) {
val sizer = program.memsizer
if (decl.datatype.size(sizer) > 1) {
errors.err("due to internal compiler complexity, currently pointer variables with data type size > 1 cannot be const", decl.position)
}
}
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
@@ -470,14 +550,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(!(valueDt istype decl.datatype)) {
val cast = numval.cast(decl.datatype.base, true)
if (cast.isValid) {
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
return listOf(AstReplaceNode(numval, cast.valueOrZero(), decl))
}
}
}
return noModifications
}
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<AstModification> {
val count = (repeatLoop.iterations as? NumericLiteral)?.number
if(count!=null && floor(count)!=count) {
val integer = NumericLiteral.optimalInteger(count.toInt(), repeatLoop.position)
@@ -487,12 +567,12 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
val constValue = typecast.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
return listOf(AstReplaceNode(typecast, constValue, parent))
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
override fun after(subroutine: Subroutine, parent: Node): Iterable<AstModification> {
val address = subroutine.asmAddress?.address
if(address!=null) {
val constAddress = address.constValue(program)
@@ -504,47 +584,30 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
val newExprLeft: Expression?,
val newExprRight: Expression?,
val newSubexprLeft: Expression?,
val newSubexprRight: Expression?
): IAstModification {
override fun perform() {
if(exprOperator!=null) expr.operator = exprOperator
if(newExprLeft!=null) expr.left = newExprLeft
if(newExprRight!=null) expr.right = newExprRight
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
if(newSubexprRight!=null) subExpr.right = newSubexprRight
}
}
private fun groupTwoFloatConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
subrightIsConst: Boolean): AstModification?
{
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
if(expr.operator==subExpr.operator) {
// both operators are the same.
// If associative, we can simply shuffle the const operands around to optimize.
if(expr.operator in AssociativeOperators && maySwapOperandOrder(expr)) {
// If commutative, we can simply shuffle the const operands around to optimize.
if(expr.operator in CommutativeOperators && expr.maySwapOperandOrder()) {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
AstShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
else
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
AstShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
} else {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
AstShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
else
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
AstShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
}
}
@@ -552,9 +615,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(expr.operator=="-" || expr.operator=="/") {
if(leftIsConst) {
return if (subleftIsConst) {
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
AstShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
} else {
IAstModification.ReplaceNode(expr,
AstReplaceNode(expr,
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position),
@@ -562,9 +625,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
} else {
return if(subleftIsConst) {
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
AstShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
AstReplaceNode(expr,
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
@@ -594,7 +657,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"/",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
@@ -609,7 +672,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program) issimpletype BaseDataType.FLOAT) {
@@ -628,7 +691,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
@@ -643,7 +706,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
@@ -661,7 +724,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
@@ -676,7 +739,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
@@ -694,7 +757,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"-",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
@@ -709,7 +772,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
return AstReplaceNode(expr, change, expr.parent)
}
}
@@ -717,3 +780,27 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
}
}
}
/**
* Custom modification to shuffle operands in binary expressions.
* This is specific to constant folding optimization.
*/
private class AstShuffleOperands(
val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
val newExprLeft: Expression?,
val newExprRight: Expression?,
val newSubexprLeft: Expression?,
val newSubexprRight: Expression?
) : AstModification() {
override val affectedNodes = listOf(expr, subExpr)
override fun perform() {
if(exprOperator!=null) expr.operator = exprOperator
if(newExprLeft!=null) expr.left = newExprLeft
if(newExprRight!=null) expr.right = newExprRight
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
if(newSubexprRight!=null) subExpr.right = newSubexprRight
}
}
@@ -3,8 +3,7 @@ package prog8.optimizer
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.*
import prog8.code.core.*
import prog8.compiler.CallGraph
@@ -18,12 +17,22 @@ class VarConstantValueTypeAdjuster(
private lateinit var callGraph : CallGraph
override fun before(program: Program) : Iterable<IAstModification> {
override fun before(program: Program) : Iterable<AstModification> {
callGraph = CallGraph(program)
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
private fun getConstantInitializer(expr: Expression): Expression? {
val cv = expr.constValue(program)
if (cv != null) return cv
if (expr is MemorySlabRef) return expr
if (expr is IFunctionCall && expr.isMemoryCall) return expr
return null
}
private fun isConstantInitializer(expr: Expression): Boolean = getConstantInitializer(expr) != null
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
@@ -39,9 +48,10 @@ class VarConstantValueTypeAdjuster(
// replace variables by constants, if possible
if(options.optimize) {
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || !decl.datatype.isNumericOrBool)
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE
|| !(decl.datatype.isNumericOrBool || decl.datatype.isPointer))
return noModifications
if (decl.value != null && decl.value!!.constValue(program) == null)
if (decl.value != null && !isConstantInitializer(decl.value!!))
return noModifications
val usages = callGraph.usages(decl)
val (writes, reads) = usages
@@ -49,10 +59,13 @@ class VarConstantValueTypeAdjuster(
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|| it.parent is AssignTarget
|| it.parent is ForLoop
|| it.parent is AddressOf
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
}
// If the address of the variable is taken, we cannot replace it with a constant.
if (reads.any { it.parent is AddressOf })
return noModifications
var singleAssignment: Assignment? = null
val singleWrite=writes.singleOrNull()
if(singleWrite!=null) {
@@ -66,7 +79,7 @@ class VarConstantValueTypeAdjuster(
}
}
}
if (singleAssignment == null) {
if (writes.isEmpty()) {
if(reads.isEmpty()) {
@@ -76,22 +89,26 @@ class VarConstantValueTypeAdjuster(
// variable is never used AT ALL so we just remove it altogether
if ("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
return listOf(AstRemove(decl, parent as IStatementContainer))
}
}
val declValue = decl.value?.constValue(program)
if (declValue != null) {
val declValue = if (decl.value != null) getConstantInitializer(decl.value!!) else null
if (declValue != null && canBeMadeConst(decl, usages)) {
// 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.splitwordarray, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.alignment, decl.dirty, decl.position)
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
val const = VarDecl.builder(decl.datatype, decl.position)
.copyFrom(decl)
.type(VarDeclType.CONST)
.value(declValue)
.build()
decl.value = null
return listOf(
IAstModification.ReplaceNode(decl, const, parent)
AstReplaceNode(decl, const, parent)
)
}
}
} else {
if (singleAssignment.origin == AssignmentOrigin.VARINIT && singleAssignment.value.constValue(program) != null) {
if (singleAssignment.origin == AssignmentOrigin.VARINIT && isConstantInitializer(singleAssignment.value) && canBeMadeConst(decl, usages)) {
if(reads.isEmpty()) {
if(decl.names.size>1) {
errors.info("unused variable '${decl.name}'", decl.position)
@@ -100,37 +117,46 @@ class VarConstantValueTypeAdjuster(
if("ignore_unused" !in decl.definingBlock.options())
errors.info("removing unused variable '${decl.name}'", decl.position)
return listOf(
IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
AstRemove(decl, parent as IStatementContainer),
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
)
}
}
// variable only has a single write, and it is the initialization value, so it can be replaced with a constant, but only 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.splitwordarray, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.alignment, decl.dirty, decl.position)
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
val const = VarDecl.builder(decl.datatype, decl.position)
.copyFrom(decl)
.type(VarDeclType.CONST)
.value(singleAssignment.value)
.build()
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
AstReplaceNode(decl, const, parent),
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
)
}
}
/*
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
}
}
*/
}
return noModifications
}
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
private fun canBeMadeConst(decl: VarDecl, usages: List<Node>): Boolean {
if (decl.datatype.isPointer) {
if (decl.datatype.size(program.memsizer) > 1) return false
val derefCount = usages.count {
val p = it.parent
val gp = p.parent
it is PtrDereference || it is ArrayIndexedPtrDereference
|| p is ArrayIndexedExpression || p is DirectMemoryRead || p is DirectMemoryWrite
|| gp is DirectMemoryRead || gp is DirectMemoryWrite
}
if (derefCount > 2) return false
}
return true
}
override fun after(range: RangeExpression, parent: Node): Iterable<AstModification> {
val from = range.from.constValue(program)?.number
val to = range.to.constValue(program)?.number
val step = range.step.constValue(program)?.number
@@ -160,7 +186,7 @@ class VarConstantValueTypeAdjuster(
return noModifications
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
// choose specific builtin function for the given types
val func = functionCallExpr.target.nameInSource
if(func==listOf("clamp")) {
@@ -179,7 +205,7 @@ class VarConstantValueTypeAdjuster(
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
}
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
@@ -209,7 +235,7 @@ class VarConstantValueTypeAdjuster(
errors.err("expected numeric arguments", functionCallExpr.args[0].position)
return noModifications
}
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
@@ -224,14 +250,14 @@ class VarConstantValueTypeAdjuster(
dt.isLong -> "abs__long"
dt.isFloat -> "abs__float"
dt.isUnsignedByte || dt.isUnsignedWord -> {
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
return listOf(AstReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
}
else -> {
errors.err("expected numeric argument", functionCallExpr.args[0].position)
return noModifications
}
}
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
@@ -247,17 +273,21 @@ class VarConstantValueTypeAdjuster(
dt.isLong -> {
val value = functionCallExpr.args[0].constValue(program)?.number
if(value!=null && value<0) {
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
errors.err("expected positive integer or float numeric argument", functionCallExpr.args[0].position)
return noModifications
}
"sqrt__long"
}
dt.isSigned -> {
errors.err("expected unsigned (positive) numeric argument", functionCallExpr.args[0].position)
return noModifications
}
else -> {
errors.err("expected numeric argument", functionCallExpr.args[0].position)
return noModifications
}
}
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
@@ -266,21 +296,15 @@ class VarConstantValueTypeAdjuster(
val t1 = functionCallExpr.args[0].inferType(program)
if(t1.isLong) {
val replaceFunc = func[0]+"__long"
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
// choose specific builtin function for the given types
val func = functionCallStatement.target.nameInSource
if(func==listOf("divmod")) {
val argTypes = functionCallStatement.args.map {it.inferType(program)}.toSet()
val argTypes = functionCallExpr.args.mapTo(mutableSetOf()) { it.inferType(program) }
if(argTypes.size!=1) {
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
errors.err("expected all ubyte/uword or all byte/word arguments", functionCallExpr.args[0].position)
return noModifications
}
val t1 = argTypes.single()
@@ -289,33 +313,16 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = when {
dt.isUnsignedByte -> "divmod__ubyte"
dt.isUnsignedWord -> "divmod__uword"
dt.isSignedByte -> "divmod__byte"
dt.isSignedWord -> "divmod__word"
else -> {
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
errors.err("expected all ubyte/uword or all byte/word arguments", functionCallExpr.args[0].position)
return noModifications
}
}
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
functionCallStatement))
}
}
else if(func==listOf("swap")) {
val t1 = functionCallStatement.args[0].inferType(program)
if(t1.isKnown) {
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
val replaceFunc = when {
dt.isByteOrBool -> "swap__byte"
dt.isWord || dt.isPointer -> "swap__word"
dt.isLong -> "swap__long"
dt.isFloat -> "swap__float"
else -> {
errors.err("expected numeric arguments", functionCallStatement.args[0].position)
return noModifications
}
}
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
functionCallStatement))
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
functionCallExpr))
}
}
return noModifications
@@ -330,31 +337,81 @@ internal class ConstantIdentifierReplacer(
private val errors: IErrorReporter
) : AstWalker() {
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
val constValue = addressOf.constValue(program)
if(constValue!=null) {
return listOf(IAstModification.ReplaceNode(addressOf, constValue, parent))
return listOf(AstReplaceNode(addressOf, constValue, parent))
}
return noModifications
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
// 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 || identifier.parent is Alias)
if (identifier.parent is AssignTarget || identifier.parent is Alias)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
if (forloop == null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
if (forloop != null && identifier === forloop.loopVar)
return noModifications
val dt = identifier.inferType(program)
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
if (!dt.isKnown || !(dt.isNumeric || dt.isBool || dt.isPointer))
return noModifications
// Special case: CONST variable holding memory() slab - replace with the MemorySlabRef itself
// This must be checked BEFORE constValue() since memory() returns null from constValue()
// It also handles the pre-desugared state (memory() still as FunctionCallExpression)
// because CodeDesugarer runs after ConstantIdentifierReplacer in processAst.
val targetNode = identifier.definingScope.lookup(identifier.nameInSource)
if (targetNode is VarDecl && targetNode.type == VarDeclType.CONST &&
identifier.parent !is ArrayIndexedExpression
) {
val slabName = when (val value = targetNode.value) {
is MemorySlabRef -> value.slabName
is FunctionCallExpression -> {
if (value.isMemoryCall) {
val str = value.args[0] as? StringLiteral
str?.value
} else null
}
else -> null
}
if (slabName != null) {
val newVal = MemorySlabRef(slabName, identifier.position)
return listOf(
AstReplaceNode(
identifier,
newVal,
identifier.parent
)
)
}
}
// handle const pointers
if (dt.isPointer) {
if (isPartOfPointerArithmetic(identifier, dt))
return noModifications // ConstantFoldingOptimizer will take care of this further
if (targetNode is VarDecl && targetNode.type == VarDeclType.CONST) {
val pointerValue = targetNode.value?.constValue(program)
if (pointerValue != null) {
return listOf(
AstReplaceNode(
identifier,
NumericLiteral(BaseDataType.UWORD, pointerValue.number, identifier.position),
parent
)
)
}
}
}
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
@@ -363,10 +420,10 @@ internal class ConstantIdentifierReplacer(
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
listOf(AstReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
listOf(AstReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
}
when {
@@ -374,19 +431,41 @@ internal class ConstantIdentifierReplacer(
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
AstReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
)
}
cval.type.isPointer -> {
return listOf(
AstReplaceNode(
identifier,
NumericLiteral(BaseDataType.UWORD, cval.number, identifier.position),
identifier.parent
)
)
}
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
}
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
private fun isPartOfPointerArithmetic(identifier: IdentifierReference, dt: InferredTypes.InferredType): Boolean {
if(dt.isPointer) {
if (identifier.parent is ArrayIndexedExpression || identifier.parent is AddressOf)
return true
val binexpr = identifier.parent as? BinaryExpression
if (binexpr != null)
return binexpr.operator == "+" || binexpr.operator == "-"
}
return false
}
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
errors.err("recursive var declaration", decl.position)
@@ -396,7 +475,7 @@ internal class ConstantIdentifierReplacer(
if(decl.isArray && decl.type==VarDeclType.MEMORY && decl.value !is IdentifierReference) {
val memaddr = decl.value?.constValue(program)
if(memaddr!=null && memaddr !== decl.value) {
return listOf(IAstModification.SetExpression(
return listOf(AstSetExpression(
{ decl.value = it }, memaddr, decl
))
}
@@ -409,7 +488,7 @@ internal class ConstantIdentifierReplacer(
// for arrays that have no size specifier attempt to deduce the size
val arrayval = decl.value as? ArrayLiteral
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
return listOf(AstSetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteral.optimalInteger(arrayval.value.size, decl.position),
decl
@@ -424,13 +503,13 @@ internal class ConstantIdentifierReplacer(
val litval = decl.value as? NumericLiteral
if (litval!=null && litval.type.isIntegerOrBool) {
val newValue = NumericLiteral(BaseDataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
return listOf(AstReplaceNode(decl.value!!, newValue, decl))
}
}
decl.datatype.isArray -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
return listOf(AstReplaceNode(decl.value!!, replacedArrayInitializer, decl))
}
else -> {
// nothing to do for this type
@@ -441,18 +520,21 @@ internal class ConstantIdentifierReplacer(
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
// 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.getOrUndef(),
ZeropageWish.DONTCARE, SplitWish.NOSPLIT, null, "dummy", emptyList(),
assignment.value, false, 0u, false, Position.DUMMY)
val decl = VarDecl.builder(targetDatatype.getOrUndef(), assignment.value.position)
.names("dummy")
.origin(VarDeclOrigin.ARRAYLITERAL)
.splitwordarray(SplitWish.NOSPLIT)
.value(assignment.value)
.build()
val replaceValue = createConstArrayInitializerValue(decl)
if(replaceValue!=null) {
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
return listOf(AstReplaceNode(assignment.value, replaceValue, assignment))
}
}
}
@@ -5,8 +5,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.*
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.floor
@@ -14,15 +13,15 @@ import kotlin.math.log2
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
val mods = mutableListOf<AstModification>()
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null && typecast.type.isBasic) {
val newLiteral = literal.cast(typecast.type.base, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
mods += AstReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
}
@@ -31,35 +30,35 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (subTypecast != null) {
// remove the sub-typecast if its datatype is larger than the outer typecast
if(subTypecast.type.largerSizeThan(typecast.type)) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
mods += AstReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
mods += AstReplaceNode(typecast, typecast.expression, parent)
}
}
return mods
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
override fun after(ifElse: IfElse, parent: Node): Iterable<AstModification> {
val truepart = ifElse.truepart
val elsepart = ifElse.elsepart
if(truepart.isNotEmpty() && elsepart.isNotEmpty()) {
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
AstInsert.after(ifElse, elsepart, parent as IStatementContainer),
AstReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
val invertedCondition = invertCondition(ifElse.condition, program)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
AstReplaceNode(ifElse.condition, invertedCondition, ifElse),
AstInsert.after(ifElse, truepart, parent as IStatementContainer),
AstReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
AstReplaceNode(truepart, elsepart, ifElse)
)
}
}
@@ -73,16 +72,16 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
val bytevalue = NumericLiteral(BaseDataType.UBYTE, (rightNum.number.toInt() shr 8).toDouble(), booleanCondition.right.position)
return listOf(
IAstModification.ReplaceNode(booleanCondition.left, msb, booleanCondition),
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
AstReplaceNode(booleanCondition.left, msb, booleanCondition),
AstReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
}
else if ((rightNum.number.toInt() and 0xff00) == 0) {
// if WORD & $00ff -> if lsb(WORD) & $ff
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
val bytevalue = NumericLiteral(BaseDataType.UBYTE, rightNum.number, booleanCondition.right.position)
return listOf(
IAstModification.ReplaceNode(booleanCondition.left, lsb, booleanCondition),
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
AstReplaceNode(booleanCondition.left, lsb, booleanCondition),
AstReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
}
}
}
@@ -90,12 +89,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
override fun after(expr: BinaryExpression, parent: Node): Iterable<AstModification> {
if(expr.operator==".")
return noModifications
val newExpr = applyAbsorptionLaws(expr)
if(newExpr!=null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return listOf(AstReplaceNode(expr, newExpr, parent))
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
@@ -107,7 +106,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
return listOf(AstReplaceNode(
expr,
BinaryExpression(expr.left, "-", (expr.right as PrefixExpression).expression, expr.position),
parent
@@ -116,7 +115,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// (-A) + X --> X - A
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
return listOf(AstReplaceNode(
expr,
BinaryExpression(expr.right, "-", (expr.left as PrefixExpression).expression, expr.position),
parent
@@ -125,7 +124,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// X - (-A) --> X + A
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
return listOf(AstReplaceNode(
expr,
BinaryExpression(expr.left, "+", (expr.right as PrefixExpression).expression, expr.position),
parent
@@ -149,7 +148,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteral(leftDt.base, 1.0, y.position), y.position)
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
return listOf(AstReplaceNode(expr, replacement, parent))
}
} else {
// Y*X - X -> X*(Y - 1)
@@ -159,7 +158,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteral(leftDt.base, 1.0, y.position), y.position)
val replacement = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
return listOf(AstReplaceNode(expr, replacement, parent))
}
}
} else if (rightBinExpr?.operator == "*") {
@@ -171,7 +170,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteral.optimalInteger(1, y.position), y.position)
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
return listOf(AstReplaceNode(expr, replacement, parent))
}
}
}
@@ -183,10 +182,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
if (expr.operator == "<=" && rightExpr.operator == "-") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
return listOf(AstReplaceNode(rightExpr, rightExpr.left, expr))
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
return listOf(AstReplaceNode(rightExpr, rightExpr.left, expr))
}
}
}
@@ -194,45 +193,45 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(!leftDt.isFloat && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
return listOf(AstReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
}
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
if(leftDt.isSigned && !leftDt.isFloat && rightVal?.number==-1.0) {
if(expr.operator=="<=") {
expr.operator = "<"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
return listOf(AstReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
} else if(expr.operator==">") {
expr.operator = ">="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
return listOf(AstReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
}
}
if (leftDt.isUnsignedByte || leftDt.isUnsignedWord) {
if(expr.operator == ">=" && rightVal?.number == 0.0) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
return listOf(AstReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
}
else if(expr.operator == ">" && rightVal?.number == 0.0) {
// unsigned > 0 --> unsigned != 0
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
return listOf(AstSetExpression({expr.operator="!="}, expr, parent))
}
}
if(!leftDt.isFloat && expr.operator == "<" && rightVal?.number == 1.0) {
// for integers: x < 1 --> x <= 0
expr.operator = "<="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
return listOf(AstReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
}
if (leftDt.isUnsignedByte || leftDt.isUnsignedWord) {
if(expr.operator == "<" && rightVal?.number == 0.0) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
return listOf(AstReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
}
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
// unsigned <= 0 --> unsigned==0
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
return listOf(AstSetExpression({expr.operator="=="}, expr, parent))
}
}
@@ -241,7 +240,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(rightDt.isBool && leftDt.isBool) {
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool==true) {
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
return listOf(AstReplaceNode(expr, expr.left, parent))
}
}
if (rightVal?.number == 1.0) {
@@ -249,7 +248,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
return listOf(AstReplaceNode(expr.right, right, expr))
}
}
}
@@ -258,7 +257,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
return listOf(AstReplaceNode(expr.right, right, expr))
}
}
}
@@ -267,7 +266,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(rightDt.isBool && leftDt.isBool) {
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool==false) {
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
listOf(AstReplaceNode(expr, expr.left, parent))
}
}
if (rightVal?.number == 1.0) {
@@ -275,7 +274,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
return listOf(AstReplaceNode(expr.right, right, expr))
}
}
}
@@ -284,7 +283,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
return listOf(AstReplaceNode(expr.right, right, expr))
}
}
}
@@ -307,7 +306,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
return listOf(AstReplaceNode(expr, result, parent))
}
else if(rightVal!=null) {
val result = if(rightVal.asBooleanValue) {
@@ -325,7 +324,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
return listOf(AstReplaceNode(expr, result, parent))
}
}
@@ -336,7 +335,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// msb(word<<8) --> lsb(word)
val msb = expr.parent as? IFunctionCall
if(msb?.target?.nameInSource == listOf("msb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
@@ -349,14 +348,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, lsb, expr.parent.parent))
}
}
16.0 -> {
// msw(long<<16) --> lsw(long)
val msw = expr.parent as? IFunctionCall
if(msw?.target?.nameInSource == listOf("msw"))
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
@@ -365,14 +364,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
24.0 -> {
// msb(long<<24) --> lsb__long(long)
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
if(msb?.singleOrNull() in arrayOf("msb__long", "msb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("lsb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
@@ -387,13 +386,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// word>>8 as ubyte --> msb(word)
val lsb = expr.parent as? IFunctionCall
if(lsb?.target?.nameInSource == listOf("lsb"))
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true)
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
}
@@ -405,14 +404,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// lsb(long>>8) --> msb(lsw(long))
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
}
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true) {
// long>>8 as ubyte --> msb(lsw(long))
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
16.0 -> {
@@ -420,13 +419,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// long>>16 as uword --> msw(long)
val lsw = expr.parent as? IFunctionCall
if(lsw?.target?.nameInSource == listOf("lsw"))
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isWord==true)
return listOf(IAstModification.ReplaceNode(expr.parent,
return listOf(AstReplaceNode(expr.parent,
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
expr.parent.parent))
@@ -436,7 +435,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// long>>16 as ubyte --> msb(long)
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, lsb, expr.parent.parent))
}
}
24.0 -> {
@@ -444,13 +443,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
// lsb(long>>24) --> msb(long)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
}
val castbyte = expr.parent as? TypecastExpression
if(castbyte?.type?.isByte==true) {
// long>>24 as ubyte --> msb(long)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
}
}
}
@@ -466,8 +465,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
leftIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
else -> null
}
}
@@ -477,8 +478,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
else -> null
}
}
@@ -488,8 +491,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
rightVal?.number==0.0 -> constFalse
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
leftIDt.isLong && leftVal?.number==-1.0 -> expr.right
else -> null
}
}
@@ -508,15 +513,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(expr.operator=="==" || expr.operator=="!=") {
val test = if (expr.operator == "==") rightVal.asBooleanValue else !rightVal.asBooleanValue
return if (test) {
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
listOf(AstReplaceNode(expr, expr.left, parent))
} else {
listOf(IAstModification.ReplaceNode(expr, invertCondition(expr.left, program), parent))
listOf(AstReplaceNode(expr, invertCondition(expr.left, program), parent))
}
}
}
if(newExpr2 != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr2, parent))
return listOf(AstReplaceNode(expr, newExpr2, parent))
if (rightVal!=null && (expr.operator == "==" || expr.operator == "!=")) {
val bitwise = expr.left as? BinaryExpression
@@ -526,11 +531,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(expr.operator=="==") {
// x & const == const -> x & const != 0 (if const has only a single bit set!)
val notZero = BinaryExpression(bitwise, "!=", NumericLiteral(rightDt.base, 0.0, rightVal.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, notZero, parent))
return listOf(AstReplaceNode(expr, notZero, parent))
} else {
// x & const != const -> x & const == 0 (if const has only a single bit set!)
val equalsZero = BinaryExpression(bitwise, "==", NumericLiteral(rightDt.base, 0.0, rightVal.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, equalsZero, parent))
return listOf(AstReplaceNode(expr, equalsZero, parent))
}
}
@@ -543,9 +548,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val bytevalue = NumericLiteral(BaseDataType.UBYTE, (andNum shr 8).toDouble(), bitwise.right.position)
val rightvalByte = NumericLiteral(BaseDataType.UBYTE, (rightVal.number.toInt() shr 8).toDouble(), rightVal.position)
return listOf(
IAstModification.ReplaceNode(bitwise.left, msb, bitwise),
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
AstReplaceNode(bitwise.left, msb, bitwise),
AstReplaceNode(bitwise.right, bytevalue, bitwise),
AstReplaceNode(expr.right, rightvalByte, expr)
)
} else if((andNum and 0xff00) == 0) {
// (WORD & $00xx)==y -> (lsb(WORD) & $xx)==y
@@ -553,9 +558,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val bytevalue = NumericLiteral(BaseDataType.UBYTE, andNum.toDouble(), bitwise.right.position)
val rightvalByte = NumericLiteral(BaseDataType.UBYTE, (rightVal.number.toInt() and 255).toDouble(), rightVal.position)
return listOf(
IAstModification.ReplaceNode(bitwise.left, lsb, bitwise),
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
AstReplaceNode(bitwise.left, lsb, bitwise),
AstReplaceNode(bitwise.right, bytevalue, bitwise),
AstReplaceNode(expr.right, rightvalByte, expr)
)
}
}
@@ -628,7 +633,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<AstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
@@ -637,7 +642,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(dt.isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
return listOf(AstReplaceNode(arrayIndexedExpression,deref, parent))
}
} else if(arrayIndexedExpression.pointerderef==null) {
// possibly pointer[0].field --> pointer.field
@@ -646,7 +651,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
if(field!=null) {
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
return listOf(AstReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
}
}
}
@@ -657,7 +662,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(dt.isPointer) {
// ptr1.ptr2[0] --> ptr1.ptr2^^
val deref = PtrDereference(ptrDeref.chain, true, ptrDeref.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, deref, parent))
return listOf(AstReplaceNode(arrayIndexedExpression, deref, parent))
}
}
}
@@ -727,7 +732,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return null
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
if(functionCallExpr.args.isEmpty())
return noModifications
@@ -736,12 +741,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val valueDt = arg.expression.inferType(program)
if (valueDt issimpletype BaseDataType.UBYTE) {
// useless lsb() of ubyte value
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
return listOf(AstReplaceNode(functionCallExpr, arg.expression, parent))
}
else if (valueDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
}
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("msw")) {
// lsb(msw(longvar)) --> @(&longvar+2) ; get the bank byte from a long variable
@@ -750,24 +755,24 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 2.0, functionCallExpr.position), functionCallExpr.position)
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
return listOf(AstReplaceNode(functionCallExpr, memread, parent))
}
} else {
if(arg is IdentifierReference && arg.nameInSource.size==2
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
// lsb(cx16.r0) -> cx16.r0L
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'L'), arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
return listOf(AstReplaceNode(functionCallExpr, highReg, parent))
}
val argDt = arg.inferType(program)
if (argDt issimpletype BaseDataType.UBYTE) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg, parent))
return listOf(AstReplaceNode(functionCallExpr, arg, parent))
}
else if (argDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
}
}
}
@@ -779,7 +784,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val valueDt = arg.expression.inferType(program)
if (valueDt issimpletype BaseDataType.BYTE || valueDt issimpletype BaseDataType.UBYTE) {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
return listOf(AstReplaceNode(
functionCallExpr,
NumericLiteral(valueDt.getOr(DataType.UBYTE).base, 0.0, arg.expression.position),
parent))
@@ -791,19 +796,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 1.0, functionCallExpr.position), functionCallExpr.position)
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
return listOf(AstReplaceNode(functionCallExpr, memread, parent))
}
} else {
if(arg is IdentifierReference && arg.nameInSource.size==2
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
// msb(cx16.r0) -> cx16.r0H
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'H'), arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
return listOf(AstReplaceNode(functionCallExpr, highReg, parent))
}
val argDt = arg.inferType(program)
if (argDt issimpletype BaseDataType.BYTE || argDt issimpletype BaseDataType.UBYTE) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
return listOf(AstReplaceNode(
functionCallExpr,
NumericLiteral(argDt.getOr(DataType.UBYTE).base, 0.0, arg.position),
parent))
@@ -815,7 +820,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
// just cast the lsb to uword
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
}
}
else if(functionCallExpr.target.nameInSource == listOf("strings", "contains")) {
@@ -823,7 +828,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(target?.value is StringLiteral) {
errors.info("for actual strings, use a regular containment check instead: 'char in string'", functionCallExpr.position)
val contains = ContainmentCheck(functionCallExpr.args[1], functionCallExpr.args[0], functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr as Node, contains, parent))
return listOf(AstReplaceNode(functionCallExpr as Node, contains, parent))
}
}
@@ -850,7 +855,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
val (expr2, _, rightVal2) = reorderCommutativeWithConstant(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteral = rightVal2
@@ -1014,7 +1019,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
val (expr2, _, rightVal2) = reorderCommutativeWithConstant(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
@@ -1177,8 +1182,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return null
}
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
if (expr.operator in AssociativeOperators && leftVal != null && maySwapOperandOrder(expr)) {
private fun reorderCommutativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
if (expr.operator in CommutativeOperators && leftVal != null && expr.maySwapOperandOrder()) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
@@ -1190,4 +1195,4 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteral?, val rightVal: NumericLiteral?)
}
}
@@ -56,6 +56,9 @@ fun Program.optimizeStatements(errors: IErrorReporter,
}
fun Program.inlineSubroutines(options: CompilationOptions): Int {
// Skip inlining when optimizations are disabled (-noopt flag)
if (!options.optimize) return 0
val inliner = Inliner(this, options)
inliner.visit(this)
return inliner.applyModifications()
+322 -157
View File
@@ -1,13 +1,12 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.ast.walk.*
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.target.VMTarget
@@ -15,13 +14,10 @@ import prog8.code.target.VMTarget
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
// inliner potentially enables *ONE LINED* subroutines, without to be inlined.
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
class DetermineInlineSubs(val program: Program): IAstVisitor {
private val modifications = mutableListOf<IAstModification>()
private val modifications = mutableListOf<AstModification>()
init {
visit(program)
@@ -30,82 +26,127 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun visit(subroutine: Subroutine) {
if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine }
fun isBodyInlineable(stmt: Return): Boolean {
if (stmt.values.isEmpty())
return true
return stmt.values.all { value ->
when (value) {
is NumericLiteral -> true
is IdentifierReference -> {
makeFullyScoped(value)
true
}
is FunctionCallExpression -> {
if (value.args.size <= 1 && value.args.all {
it is NumericLiteral || it is IdentifierReference
}) {
makeFullyScoped(value)
true
} else {
false
}
}
else -> false
}
}
}
fun isBodyInlineable(stmt: Assignment): Boolean {
return if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
val binExpr = stmt.value as BinaryExpression
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
makeFullyScoped(stmt.target.identifier!!)
makeFullyScoped(binExpr.left as IdentifierReference)
true
} else
false
} else
false
}
fun isBodyInlineable(stmt: FunctionCallStatement): Boolean {
// Allow inlining if arguments are simple (function calls will be inlined too)
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
return inline
}
fun isBodyInlineable(stmt: Jump): Boolean {
return if(stmt.target is IdentifierReference) {
makeFullyScoped(stmt.target as IdentifierReference)
true
}
else
stmt.target is NumericLiteral
}
if (!subroutine.isAsmSubroutine) {
// NOTE: We allow subroutines with ANY number of parameters to be considered for inlining.
// For void calls where the body doesn't use the parameters, inlining is trivial -
// we just remove the call entirely (no parameter substitution needed).
// For calls that return values or use parameters in the body, we need simple arguments
// (NumericLiteral or IdentifierReference) for parameter substitution to work.
// See canInlineAtCallSite() for the argument simplicity check.
val containsSubsOrVariables = subroutine.statements.any {
it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM)
}
if (!containsSubsOrVariables) {
if (subroutine.statements.size == 1 || (subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))) {
// For subroutines with parameters, there will be a VarDecl(SUBROUTINEPARAM) per parameter
// plus the body statement(s)
val expectedMaxStmts = subroutine.parameters.size + 1 // param VarDecls + 1 body statement
val hasOnlyBodyStatements = subroutine.statements.size == expectedMaxStmts ||
(subroutine.statements.size == expectedMaxStmts + 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true)
if (hasOnlyBodyStatements) {
if (subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
// TODO consider multi-value returns as well
is Return -> stmt.values.isEmpty() || stmt.values.size==1 &&
if (stmt.values[0] is NumericLiteral)
true
else if (stmt.values[0] is IdentifierReference) {
makeFullyScoped(stmt.values[0] as IdentifierReference)
true
} else if (stmt.values[0] is IFunctionCall && (stmt.values[0] as IFunctionCall).args.size <= 1 && (stmt.values[0] as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
if (stmt.values[0] is FunctionCallExpression) {
makeFullyScoped(stmt.values[0] as FunctionCallExpression)
true
}
else false
} else
false
is Assignment -> {
if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
val binExpr = stmt.value as BinaryExpression
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
makeFullyScoped(stmt.target.identifier!!)
makeFullyScoped(binExpr.left as IdentifierReference)
true
} else
false
} else
false
}
is FunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is Jump -> stmt.target is NumericLiteral || stmt.target is IdentifierReference
// Find the first non-parameter statement (the actual body)
val bodyStmt = subroutine.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
val isAutoInlineable =
when (val stmt = bodyStmt) {
is Return -> isBodyInlineable(stmt)
is Assignment -> isBodyInlineable(stmt)
is FunctionCallStatement -> isBodyInlineable(stmt)
is Jump -> isBodyInlineable(stmt)
else -> false
}
if (isAutoInlineable)
subroutine.inline = true
}
}
if (subroutine.inline && subroutine.statements.size > 1) {
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLastOrNull() // get rid of the Return, to be able to inline the (single) statement preceding it.
// Remove trailing return if it's empty and there's another body statement
val bodyStmts = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
if (bodyStmts.size > 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true) {
subroutine.statements.removeLast()
}
}
}
}
@@ -116,7 +157,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
identifier.targetStatement()?.let { target ->
val scoped = (target as INamedStatement).scopedName
val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
modifications += AstReplaceNode(identifier, scopedIdent, identifier.parent)
}
}
@@ -127,7 +168,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
modifications += AstReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -139,7 +180,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
modifications += AstReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -159,100 +200,224 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
}
override fun before(program: Program): Iterable<IAstModification> {
override fun before(program: Program): Iterable<AstModification> {
DetermineInlineSubs(program)
return super.before(program)
}
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
if(sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) {
// simply insert the asm for the argument-less routine
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
} else {
// note that we don't have to process any args, because we online inline parameterless subroutines.
when (val toInline = sub.statements.first()) {
is Return -> {
// TODO consider multi-value returns as well
if(toInline.values.size!=1)
noModifications
else {
val fcall = toInline.values[0] as? FunctionCallExpression
if(fcall!=null) {
// insert the function call expression as a void function call directly
sub.hasBeenInlined=true
val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position)
listOf(IAstModification.ReplaceNode(origNode, call, parent))
} else
noModifications
}
}
else -> {
if(origNode !== toInline) {
sub.hasBeenInlined = true
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
} else
noModifications
}
}
}
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<AstModification> {
val sub = functionCallStatement.target.targetStatement(program.builtinFunctions) as? Subroutine
return if(sub==null || !canInline(sub, functionCallStatement))
noModifications
else
possibleInlineFcallStmt(sub, functionCallStatement, parent)
if (sub == null) return noModifications
if (!sub.inline) return noModifications
val (canInline, _) = canInlineAtCallSiteWithReason(sub, functionCallStatement)
if (!canInline) {
// println(">>> INLINER: NOT inlining '${sub.name}' at ${functionCallStatement.position}: $reason")
return noModifications
}
return possiblyInlineFunctioncallStmt(sub, functionCallStatement, parent)
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty() && canInline(sub, functionCallExpr)) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
private fun canInlineAtCallSiteWithReason(sub: Subroutine, fcall: IFunctionCall): Pair<Boolean, String> {
if (!sub.inline)
return false to "subroutine not marked as inlineable"
// Check parameter argument complexity
if (sub.parameters.isNotEmpty()) {
// Only allow inlining for void statement calls, not expression context
if (fcall !is FunctionCallStatement || !fcall.void)
return false to "expression context calls with parameters not supported yet"
// Only inline if all arguments are simple (literals or identifiers)
if (!fcall.args.all { it is NumericLiteral || it is IdentifierReference }) {
val complexArgs = fcall.args.filterNot { it is NumericLiteral || it is IdentifierReference }
return false to "complex arguments (${complexArgs.size}x): ${complexArgs.map { it::class.simpleName }.joinToString(", ")}"
}
return if(sub.isAsmSubroutine) {
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
noModifications
} else {
when (val toInline = sub.statements.first()) {
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.
// TODO consider multi-value returns as well
if(toInline.values.size==1 && functionCallExpr!==toInline.values[0]) {
sub.hasBeenInlined=true
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.values[0].copy(), parent))
}
else
noModifications
}
else -> noModifications
}
if (options.compTarget.name != VMTarget.NAME) {
// Get the first non-parameter statement (skip VarDecl with SUBROUTINEPARAM origin)
val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
if (bodyStmt is IFunctionCall) {
val existing = (fcall as Node).definingScope.lookup(bodyStmt.target.nameInSource[0])
if (existing is VarDecl || existing is StructFieldRef) {
return false to "call target conflicts with existing symbol"
}
}
}
return true to "OK"
}
private fun canInlineAtCallSite(sub: Subroutine, fcall: IFunctionCall): Boolean {
return canInlineAtCallSiteWithReason(sub, fcall).first
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine
if (sub == null) return noModifications
if (!sub.inline) return noModifications
val (canInline, _) = canInlineAtCallSiteWithReason(sub, functionCallExpr)
if (!canInline) {
// println(">>> INLINER: NOT inlining '${sub.name}' at ${functionCallExpr.position}: $reason")
return noModifications
}
// Get the first non-parameter statement (skip VarDecl with SUBROUTINEPARAM origin)
val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
val hasOnlyBodyAndReturn = bodyStmt != null && (
(sub.statements.count { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } == 1) ||
(sub.statements.count { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } == 2 &&
sub.statements.lastOrNull()?.let(::isEmptyReturn) == true)
)
if (!hasOnlyBodyAndReturn) {
// println(">>> INLINER: NOT inlining '${sub.name}' at ${functionCallExpr.position}: body too complex")
return noModifications
}
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
// note that we don't have to process any args, because we are currently only inlining parameterless subroutines.
return if(toInline.values.size==1 && functionCallExpr!==toInline.values[0]) {
// println(">>> INLINER: INLINED expression '${sub.name}' at ${functionCallExpr.position} (return value substituted)")
sub.hasBeenInlined =true
listOf(AstReplaceNode(functionCallExpr, toInline.values[0].copy(), parent))
}
else
noModifications
}
return if(sub.isAsmSubroutine) {
// cannot inline assembly directly in the Ast here as an Asm node is not an expression... it will be done later.
noModifications
} else {
when (val toInline = bodyStmt) {
is Return -> inlineFunctionBody(toInline)
else -> noModifications
}
}
}
private fun possiblyInlineFunctioncallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<AstModification> {
fun possiblyShortCircuitFunctionCall(toInline: Return): Iterable<AstModification> {
val functionCalls = toInline.values.filterIsInstance<FunctionCallExpression>()
if (functionCalls.isEmpty()) {
// No function calls in the return values - the void call has no side effects and can be removed.
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (removed, no side effects)")
sub.hasBeenInlined = true
return listOf(AstRemove(origNode as Statement, parent as IStatementContainer))
}
// There are function calls in the return values - convert each to a void statement.
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (converted ${functionCalls.size} inner call(s) to void)")
sub.hasBeenInlined = true
val voidStatements: MutableList<Statement> = functionCalls.map { fcall ->
FunctionCallStatement(
fcall.target.copy(),
fcall.args.map { it.copy() }.toMutableList(),
true,
fcall.position
)
}.toMutableList()
// Wrap multiple statements in AnonymousScope and replace the original call node.
val scope = AnonymousScope(voidStatements, origNode.position)
return listOf(AstReplaceNode(origNode, scope, parent))
}
fun possiblyInlineFunctionBody(toInline: Statement): Iterable<AstModification> {
// For void calls with parameters, we already verified in canInlineAtCallSiteWithReason()
// that the parameter is unused in the body (so no substitution needed).
// For expression context calls with parameters, those are rejected in canInlineAtCallSiteWithReason()
// so we never reach here.
// Just copy the statement - parameters are unused so no substitution needed.
val inlinedStatement = toInline.copy()
// println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted, ${sub.parameters.size} param(s))")
return if(origNode !== toInline) {
sub.hasBeenInlined = true
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
} else
noModifications
}
if(sub.inline && sub.parameters.size >= 0) {
// Get the first non-parameter statement (skip VarDecl with SUBROUTINEPARAM origin)
val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
val hasOnlyBodyAndReturn = bodyStmt != null && (
(sub.statements.count { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } == 1) ||
(sub.statements.count { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } == 2 &&
sub.statements.lastOrNull()?.let(::isEmptyReturn) == true)
)
require(hasOnlyBodyAndReturn) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) {
sub.hasBeenInlined=true
listOf(AstReplaceNode(origNode, bodyStmt.copy(), parent))
} else {
// note that we don't have to process any args, because we only inline parameterless subroutines.
when (val toInline = bodyStmt) {
is Return -> possiblyShortCircuitFunctionCall(toInline)
else -> possiblyInlineFunctionBody(toInline)
}
}
}
return noModifications
}
private fun canInline(sub: Subroutine, fcall: IFunctionCall): Boolean {
if (!sub.inline)
return false
if (options.compTarget.name != VMTarget.NAME) {
val stmt = sub.statements.single()
if (stmt is IFunctionCall) {
val existing = (fcall as Node).definingScope.lookup(stmt.target.nameInSource.take(1))
return existing !is VarDecl && existing !is StructFieldRef
/**
* Substitutes parameter references in a statement with argument values.
*/
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
// Handle multi-value assignments: split `a, b = func()` into separate assignments
val multiTargets = assignment.target.multi ?: return noModifications
val fcall = assignment.value as? FunctionCallExpression ?: return noModifications
val sub = fcall.target.targetStatement(program.builtinFunctions) as? Subroutine
?: return noModifications
// Only handle parameterless subroutines with simple returns
if (!sub.inline || sub.parameters.isNotEmpty() || !canInlineAtCallSite(sub, fcall))
return noModifications
val toInline = sub.statements.firstOrNull() as? Return ?: return noModifications
// Check return count matches target count
if (toInline.values.size != multiTargets.size)
return noModifications
// Only allow simple return values (literals or identifiers, no function calls)
if (!toInline.values.all { it is NumericLiteral || it is IdentifierReference })
return noModifications
// Create multiple single assignments, skipping void targets (they discard the value)
val newAssignments = multiTargets.zip(toInline.values)
.filter { (target, _) -> !target.void }
.map { (target, value) ->
Assignment(
target = AssignTarget(
identifier = target.identifier!!.copy(),
arrayindexed = null,
memoryAddress = null,
multi = null,
void = false,
position = target.position
),
value = value.copy(),
origin = assignment.origin,
position = assignment.position
)
}
}
return true
// println(">>> INLINER: INLINED multi-return call to '${sub.name}' at ${assignment.position} (split into ${newAssignments.size} separate assignments)")
sub.hasBeenInlined = true
val scope = AnonymousScope(newAssignments.toMutableList(), assignment.position)
return listOf(AstReplaceNode(assignment, scope, parent))
}
}
Loaded 100 of 374 files, more files were not shown because too many files have changed in this diff. Show more