Compare commits

...

686 Commits
v6.2 ... v7.5

Author SHA1 Message Date
35998142fe version 7.5 2021-12-10 20:18:17 +01:00
75ea453bf4 fix asm code optimization problem caused in previous release where asm file is not read back in separate lines anymore 2021-12-10 15:37:53 +01:00
33061aaa0d fix: allow scoped variables such as cx16.rX as loop variable in forloops 2021-12-10 14:59:04 +01:00
e342311bef fix wrong code for inplace modification of a pointervariable's memory value 2021-12-10 14:48:53 +01:00
3d743a1ba1 added more constfolding 2021-12-09 23:32:48 +01:00
abca618008 added more constfolding 2021-12-09 23:12:12 +01:00
0d2c3901a3 added more constfolding 2021-12-09 22:12:31 +01:00
d901a1531f added missing vectors to syslib 2021-12-09 21:38:00 +01:00
d8d56b195f comments 2021-12-09 21:13:13 +01:00
a52699717c Merge remote-tracking branch 'origin/master'
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestSubroutines.kt
#	examples/test.p8
2021-12-07 23:29:30 +01:00
98315de723 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:28:45 +01:00
68d2f7d4c0 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:21:49 +01:00
c812b5ee09 elaborate pointervar indexing a bit more in the docs 2021-12-07 22:25:14 +01:00
dcf487bdc1 fix: correctly insert return statement if needed to prevent 'fall through' into following subroutine
this wasn't working correctly anymore when the last statement before the subroutine was a jump/goto
2021-12-07 21:34:50 +01:00
547b1d3720 comment corrections 2021-12-06 23:33:18 +01:00
84f75f4156 tweaked some more .getOrElse 2021-12-06 21:22:00 +01:00
ff69da3fa2 error when 'else' choice in when statemetn isn't the last one, also generate slightly better code for when statements 2021-12-05 21:54:46 +01:00
edffe92a24 astchecker is smarter in detecting rts in inline assembly 2021-12-05 21:28:31 +01:00
b6fe40ada4 fix: cx16.r0 now properly treated as zeropage var on cx16 so @(cx16.r0) won't copy it to temp var anymore 2021-12-05 21:21:41 +01:00
837804b231 test for string x and u escape sequences 2021-12-05 18:39:34 +01:00
81deed143b fix grammar problem: \x and \u escape sequences didn't work in character literals. 2021-12-05 18:11:40 +01:00
900cdd3fa1 added cx16diskio with load() and load_raw() that are HIMEM bank-aware 2021-12-05 02:20:48 +01:00
0018dc6ce7 refactor machinedefinition 2021-12-04 19:07:19 +01:00
c92f914081 gradle build settings tweak to avoid jdk version conflict 2021-12-04 18:36:47 +01:00
0498444ef2 moved all unit tests into single project to avoid dependency issues 2021-12-04 18:20:22 +01:00
ce3c34e458 tweak in error output for file links, corrected column number off-by-one 2021-12-04 16:52:03 +01:00
20401b99d8 added cx16.getrambank() / getrombank() to retrieve the current ram and rom bank numbers. 2021-12-04 15:27:54 +01:00
397f98513b optimize loading A from pointervar 2021-12-04 05:36:48 +01:00
e545ea9504 fix and optimize storing A into pointervar 2021-12-04 04:43:58 +01:00
b867d8f731 cleanups 2021-12-04 01:03:51 +01:00
9a68864b67 version 7.5-dev 2021-12-04 00:18:44 +01:00
72d7178762 added diskio.load_raw() to load headerless files 2021-12-04 00:07:21 +01:00
fbcd9a0c1d reduce number of similar errors for type problem in assignment 2021-12-02 17:44:52 +01:00
c3144a20db spacing 2021-12-02 00:10:06 +01:00
5b56e0462d also deal with zero args 2021-12-01 22:26:36 +01:00
b7fffbb6df release 7.4.1 - oops, funcion call arg count validation was broken 2021-12-01 21:44:03 +01:00
1f346230e3 release 7.4 2021-11-30 22:50:12 +01:00
a2860a7c8c todo 2021-11-30 22:45:43 +01:00
df997e5d3b don't write the asm file twice 2021-11-30 03:47:57 +01:00
a67a82c921 tweak 2021-11-30 03:05:25 +01:00
ea0fe8d3d2 PrefixExpression doesn't cause clobber risk 2021-11-30 02:32:37 +01:00
2560042ac7 fix compiler crashes on in-place operations on cx16 registers or invalid signed types 2021-11-30 02:27:37 +01:00
3d1d0696b9 refactor compiler arguments passing 2021-11-30 01:40:21 +01:00
83f893f50b doc 2021-11-30 00:54:03 +01:00
9ecf95b075 fix syntaxerror in const processing of ranges if it contained variables 2021-11-29 23:36:41 +01:00
7748c261da rsave/rrestore moved from sys to builtin function to solve the stack related problem when calling it as a regular subroutine 2021-11-29 23:13:04 +01:00
a2db44f80c also consider Y register for clobber check for functioncall arguments 2021-11-29 22:09:05 +01:00
b438d8aec0 fix invalid range size check when stepval is not a positive integer 2021-11-29 02:01:19 +01:00
4ac169b210 formatting 2021-11-29 01:25:21 +01:00
56dc6d7f1e comment 2021-11-29 01:10:11 +01:00
45b8762188 use inc/ina instead of adc 2021-11-29 00:07:15 +01:00
cafab98d10 correction 2021-11-28 18:59:36 +01:00
9256f910f0 rollback binexpr splitting, caused slowdowns 2021-11-28 18:50:05 +01:00
32068a832a split some additional binary expressions to avoid stack-based evaluation 2021-11-28 18:27:28 +01:00
47c2c0376a added some cpu stack related assembly-level optimizations 2021-11-28 17:27:01 +01:00
f0dadc4a43 optimize 1-arg functioncalls 2021-11-28 16:55:10 +01:00
960b60cd2d tweak 2021-11-28 14:06:12 +01:00
d6abd72e55 fix push() of signed values 2021-11-28 13:01:46 +01:00
0a568f2530 fix the check of double-defined subroutine variables 2021-11-28 12:52:32 +01:00
c52aa648c0 use an AnonymousScope to contain GoSub changes instead of adding separate statements 2021-11-28 12:09:13 +01:00
3d23b39f4c moved A to the end of the param list to avoid having to store its value 2021-11-28 04:03:18 +01:00
f3a4048ebf improved setting Carry bit as asmsub parameter 2021-11-28 03:31:32 +01:00
1b07637cc4 better error checking for wrong pop() 2021-11-28 02:49:18 +01:00
68b75fd558 fix: also allow pass-by-reference arguments to builtin functions that accept UWORD (adds implicit type cast) 2021-11-28 02:34:53 +01:00
7c5ec1853d nice error message if pop() argument is wrong 2021-11-28 02:20:35 +01:00
e8f4686430 undid failed attempt of using sys.push/sys.pop for stack args - now using new push(), pushw(), pop(), popw() builtin functions 2021-11-28 01:22:40 +01:00
02348924d0 failed attempt of using sys.push/pop for stack args 2021-11-27 23:52:47 +01:00
69dcb4dbda fix reporting of (not) unused code after GoSub jump 2021-11-27 21:22:34 +01:00
c838821615 refactor fuction arguments codegen a bit 2021-11-27 21:14:21 +01:00
8b4ac7801f fix sys.push() signature for c64 2021-11-27 20:18:41 +01:00
64a411628d doc fixes 2021-11-27 19:58:08 +01:00
e8e25c6fd6 added sys.push() and sys.pop() to put values on cpu stack. Added missing builtin functions to syntax-files. 2021-11-27 18:09:15 +01:00
62485b6851 allow assigns to asmsub parameters (registers), but this is not very useful in practice. 2021-11-27 15:41:44 +01:00
54025d2bf5 small refactor and spelling fixes 2021-11-27 14:49:18 +01:00
f5ebf79e71 make sure X register is also saved if needed when GoSub is used 2021-11-26 22:11:52 +01:00
66d5490702 just added missing FAC2 assign possibility 2021-11-26 21:34:00 +01:00
42fe052f9f got rid of old getScopedSymbolNameForTarget routine 2021-11-26 21:09:29 +01:00
58d9c46a9b got rid of old makeScopedName routine 2021-11-26 20:56:30 +01:00
e4648e2138 proper rounding of builtin functions that return int from float 2021-11-26 20:32:12 +01:00
110e047681 replace subroutine calls (statement) by GoSub 2021-11-26 19:47:01 +01:00
17d403d812 Merge branch 'ref-subroutine-param' into v7.4-dev
# Conflicts:
#	compilerAst/src/prog8/ast/AstToplevel.kt
2021-11-26 01:12:14 +01:00
0a53bd4956 fix parameter name conflict 2021-11-26 01:01:59 +01:00
e52d05c7db fix some scoping related symbol lookup issues, clarified scoping rules in docs 2021-11-23 23:43:23 +01:00
b00db4f8a2 no longer report unknown type errors as well for unknown symbols,
added a bunch more unit tests for symbol scoping rules
2021-11-23 22:45:57 +01:00
0c2f30fd45 links to 6502 bresenham line algorithms 2021-11-23 21:51:18 +01:00
e08871c637 oops! replace phx/plx 65C02 (cx16) instructions by 6502 (c64) compatible alternative.
Couldn't assemble code that used some of the routines in conv on c64 before...
2021-11-22 21:02:43 +01:00
ff715881bc allow scoped identifiers to reference a subroutine parameter directly.
also for asmsubroutines, but the asm generation for that is not yet done.
2021-11-21 23:21:39 +01:00
0e2e5ffa52 fix parameter name conflict 2021-11-21 22:12:35 +01:00
8095c4c155 added GoSub node (internal use only later for calling subroutines) 2021-11-21 16:23:48 +01:00
e86246a985 todo 2021-11-21 14:00:19 +01:00
625aaa02eb documented the compiler's command line options in more detail 2021-11-21 13:53:22 +01:00
787e35c9f3 asm optimizer can now also see of a symbol reference if it is in IO space or not (to a certain extent), so that these instructions are no longer optimized away 2021-11-21 13:12:51 +01:00
8887e6af91 fix substituting 0 only if its actually the same variable that's substituted 2021-11-21 12:34:57 +01:00
dde4c751da version 7.4-dev 2021-11-21 03:28:13 +01:00
3c39baf1d6 don't optimize seemingly redundant assembly instructions away that manipulate IO memory space 2021-11-21 03:24:03 +01:00
b292124f3c replaced many short/int values by unsigned types if appropriate 2021-11-21 00:55:56 +01:00
c0035ba1a2 char encodings now use UByte type instead of short 2021-11-21 00:07:17 +01:00
2491509c6a add assignment optimization X=value-X --> X=-X ; X+=value (to avoid need of stack-evaluation) 2021-11-20 23:43:10 +01:00
107935ed31 add some more const folding patterns 2021-11-20 22:47:49 +01:00
31491c62c5 add some more const folding patterns 2021-11-20 22:40:12 +01:00
eacf8b896a fix augmentable check to align with what the asmgen understands 2021-11-20 22:06:51 +01:00
7936fc5bd8 tiny optimization of negating a register 2021-11-20 21:42:55 +01:00
adfaddbcf4 give a nicer error when given a wrong compilation target. 2021-11-20 18:30:55 +01:00
74db5c6be7 fix referencesIdentifier() and better removal of unnecessary assignments 2021-11-20 17:41:41 +01:00
f9399bcce7 r=(q+r)-c and r=q+(r-c) are now both also 'augmentable', and BinExprSplitter doesn't check for associativeOperator anymore 2021-11-20 02:03:32 +01:00
87600b23db fix constvalue parent linkage for prefix and typecast 2021-11-20 00:20:35 +01:00
cedfb17b18 fix too aggressive removal of vars that weren't completely unused 2021-11-19 22:49:35 +01:00
fa4c83df6b added 3 tests for discovered problems 2021-11-18 23:55:20 +01:00
42c8720e8b fix float rounding tests 2021-11-18 22:54:49 +01:00
b334d89715 refactor and fix the way memory addresses are checked to be in IO space or regular ram 2021-11-18 22:47:58 +01:00
4f5d36a84d optimization added: bitwise operations with a negative constant number -> replace the number by its positive 2 complement 2021-11-18 02:51:42 +01:00
8f379e2262 give an error when initializing an integer var with a float value instead of silently rounding 2021-11-18 01:56:11 +01:00
fa11a6e18b removed faulty and too aggressive assembly optimization of double-store 2021-11-18 01:43:22 +01:00
52bedce8f4 added test for assignment.isAugmented 2021-11-18 01:05:16 +01:00
4c82af36e6 fix improperly changed behavior about =0 initializer 2021-11-18 00:17:22 +01:00
dafa0d9138 fix compiler crash bug due to reused ast expression nodes. Now all (relevant) Nodes have a copy() function to make a clone. 2021-11-17 23:05:59 +01:00
2e0450d7ed fix bug where variable=0 initializer was forgotten if vardecl is followed by an augmented assignment 2021-11-17 22:31:43 +01:00
6af3209d4d add more const foldings 2021-11-17 00:57:00 +01:00
5d362047e2 add some more comparison expression optimizations to compare against 0 if possible 2021-11-17 00:04:52 +01:00
f48d6ca9f8 simplified NumericLiteral to always just contain a Double instead of a Number for the value 2021-11-16 23:52:54 +01:00
964e8e0a17 update to Kotlin 1.6.0 2021-11-16 22:36:23 +01:00
1f60a2d8b9 comments 2021-11-15 01:30:12 +01:00
5fd83f2757 version 7.3 2021-11-14 22:55:13 +01:00
c80df4140b until-loop condition now also simplified to avoid stack-eval 2021-11-14 22:51:02 +01:00
53e1729e2f introduce option to use internal scratch variables via prog8_lib definitions (ony for compiler, not for user code!) 2021-11-14 16:01:54 +01:00
ab2d1122a9 conditional expressions are optimized more intelligently (simple ones are not split off in separate assignments) 2021-11-14 12:38:56 +01:00
5190594c8a added several more assembly-level optimizations to remove redundant instructions 2021-11-14 12:23:46 +01:00
c858ceeb58 compiler shouldn't use cx16.r15 as temp var 2021-11-14 02:38:59 +01:00
f0f52b9166 optimize typecasted binary expression to avoid even more estack use. also fix wrong parent crash in removal of unused variable's assignments. 2021-11-13 14:22:37 +01:00
00c6f74481 tweak temp float 2021-11-13 12:56:59 +01:00
2177ba0ed2 added signed versions of the cx16 virtual registers 2021-11-13 02:42:21 +01:00
3483515346 preparing for more optimizations 2021-11-12 23:23:51 +01:00
75a06d2a40 preparing for more optimizations 2021-11-12 02:17:37 +01:00
53ac11983b better unused variable removal 2021-11-11 03:03:21 +01:00
69f4a4d4f8 tweak expr.typecastTo() a bit 2021-11-11 00:15:09 +01:00
222bcb808f optimize load-store-load combo in output asm 2021-11-10 23:47:35 +01:00
686483f51a fixed division of signed byte number by 2. (!) 2021-11-10 00:17:56 +01:00
8df3da11e3 add cosr8, sinr8, cosr16 and sinr16 builtin functions that take a degree 0..179 (= 0..358 in 2 degree steps)
to more easily scale halves/quarters etc of a circle than possible with the ones that take 0..255 'degrees'.
2021-11-09 23:39:26 +01:00
84dafda0e4 fix error message for type mismatch on builtin-function parameter 2021-11-09 22:19:07 +01:00
b909facfe5 fix compiler stackoverflow crash on certain typecasted expressions containing floats. 2021-11-09 19:31:19 +01:00
7780d94de1 discovered crash related to float typecasting in asm assignment codegen 2021-11-09 03:45:07 +01:00
f2c440e466 new sin/cos idea 2021-11-09 02:38:43 +01:00
4937e004b5 fix compiler crash where it used wrong datatype in split assignment
fixes crash for "ubyte bb ;; uword ww ;; bb = not bb or not ww"
2021-11-09 01:13:23 +01:00
4cb383dccb discovered crash about storage size mismatch 2021-11-08 21:44:06 +01:00
c8a4b6f23c refactor expressionsAsmGen so that it now has just 1 single public function
this makes replacing it by a non-stack based solution easier in the future.
2021-11-08 19:21:55 +01:00
857724c7e6 attempt to make if-statement not use stack eval anymore 2021-11-08 19:07:36 +01:00
a9b0400d13 fixed 'not' operator priority: it now has higher priority as or/and/xor. 2021-11-08 18:38:04 +01:00
2d1e5bbc7e remove unimportant empty tests 2021-11-08 17:00:10 +01:00
60627ce756 kotest migration done, fixes #70 2021-11-08 16:19:24 +01:00
7961a09d16 converting compiler module's testcases to kotest assertions 2021-11-08 16:14:22 +01:00
613efcacc7 converting compiler module's testcases to kotest (ongoing) 2021-11-08 15:08:48 +01:00
7e8db16e18 moved to kotest assertions in compilerAst module tests 2021-11-07 21:18:18 +01:00
1fbbed7e23 remove unittest machinery from modules that don't have tests 2021-11-07 17:34:14 +01:00
984272beb4 migrated compilerAst module to KoTest (but not finished with the assertions yet) 2021-11-07 17:25:53 +01:00
b9ce94bb68 migrated codeGeneration module to KoTest 2021-11-07 15:40:05 +01:00
f4c4ee78d9 re-use global returnvalue temp var instead of duplicating it in every subroutine that needs it 2021-11-07 14:19:21 +01:00
793596614e attempt to fix ReadTheDocs build issue 2021-11-07 00:37:31 +01:00
136280100c attempt to fix ReadTheDocs build issue 2021-11-07 00:23:44 +01:00
29f1e4d2c9 attempt to fix ReadTheDocs build issue 2021-11-07 00:18:51 +01:00
72a7e61fd0 version 7.2 2021-11-06 23:42:13 +01:00
381cfca67f Merge branch 'v7.2'
# Conflicts:
#	compiler/res/version.txt
2021-11-06 23:41:39 +01:00
f40620aa25 "not x" as a condition (if, while, until) is optimized into "x==0", this avoids calculating the value 2021-11-06 23:25:32 +01:00
57a9fed42b todo 2021-11-06 19:09:33 +01:00
18d820da94 correct assignment type 2021-11-06 18:52:54 +01:00
26e66f046f implement some more missing codegen for inplace Prefix expressions 2021-11-06 18:48:42 +01:00
4270c04856 don't crash but give proper error on "-X" expression where X is not a signed type 2021-11-06 18:06:01 +01:00
74456d1135 optimized prefix-expression in to use stack evaluation less 2021-11-06 17:57:00 +01:00
62dc824bc0 tweaks 2021-11-06 17:14:07 +01:00
1605791f1b float swap() no longer uses evaluation stack but a single temp var instead + FAC1 2021-11-06 03:36:14 +01:00
37a46aa2cf complex memory assignment also tries to avoid estack evaluation (but not done yet) 2021-11-06 00:03:19 +01:00
1d2d217b94 non-optimized typecast assignments now attempt to not use evalstack 2021-11-05 23:25:07 +01:00
23961f695d fixed some parse tree node position end-columns. cleanup some todo's 2021-11-05 22:48:28 +01:00
730b208617 relaxed some type checks on certain word register assignment
preparing to optimize asmsub arg passing for complex expressions
2021-11-04 23:57:25 +01:00
f09c04eeac fix invalid asm addressing mode for certain value-to-evalstack transfers 2021-11-04 22:44:31 +01:00
be73739c62 todo 2021-11-03 23:08:11 +01:00
eea3fb48a8 add command line option 'optfloatx' to explicitly re-enable float expr optimization as this can increase code size significantly.
The output size of the various example programs using floating point, when not using this optimization, has been reduced significantly.
The resulting code runs a (tiny) bit slower though.
2021-11-03 22:52:08 +01:00
b4fa72c058 fix parent node linkage for reading array parameter 2021-11-03 21:57:31 +01:00
b0a865b0f1 update todo 2021-11-02 23:55:50 +01:00
7f49731618 fix: don't initialize block vars twice, fix: make sure the prog8_init_vars generated routine is correctly called when needed 2021-11-02 23:13:28 +01:00
3410aea788 fix regression: don't add 0 initializer when variable is assigned to anyway (or is loopvar in a for-loop) 2021-11-02 21:23:59 +01:00
bc0a133bb1 doc 2021-11-02 20:24:45 +01:00
7e287a5359 proper parent node linkage in generated const values out of typecast expressions. Fixes crash mentioned in #72 2021-11-02 00:47:01 +01:00
1110bd0851 fix vardecl initialization value to not use stack eval anymore but separate assignment
(this causes the optimized assignment code gen to be used instead)
but some programs now end up larger in output size
2021-11-01 00:24:15 +01:00
1b576f826d remove unneeded sibling methods 2021-10-31 16:50:15 +01:00
fe17566370 improved reporting of slow stack based evaluation code 2021-10-31 14:18:49 +01:00
e3c00669c1 fixed improved asm generation for conditions that compare signed word to zero 2021-10-31 02:39:45 +02:00
33d17afc32 improved asm generation for conditions that compare byte/word to zero 2021-10-31 01:58:16 +02:00
2388359a99 improved asm generation for conditions that compare ubyte/uword to zero 2021-10-31 01:39:37 +02:00
2df0c9503c improved asm generation for conditions that compare floats to zero 2021-10-31 01:28:08 +02:00
61fa3bc77c comparisonjump tweak 2021-10-31 00:57:22 +02:00
03ac9b6956 various cleanups, slight update to dbus 2021-10-30 19:30:19 +02:00
dfbef8495d got rid of ParsingFailedError 2021-10-30 17:05:23 +02:00
7b17c49d8f update petscii tables with improvements to box drawing chars. fixes #68 2021-10-30 16:45:23 +02:00
4b3f31c2ee added option to suppress assembler output (and enabled this in unit tests) 2021-10-30 15:26:40 +02:00
9ccc65bf8f more petscii tests 2021-10-30 15:15:11 +02:00
f9e22add03 fix crash when using array as paramater type 2021-10-30 15:15:00 +02:00
846951cda7 kotlin 1.5.31 2021-10-30 12:26:05 +02:00
97836e18b2 simplified gradle config, automatically run installDist task after build 2021-10-30 12:01:52 +02:00
7b69df4db2 todos 2021-10-30 00:38:48 +02:00
3767b4bbe7 'Program' is not an ast Node 2021-10-30 00:25:34 +02:00
d7d2eefa4f implemented CharLiteral.constValue() 2021-10-30 00:05:55 +02:00
6737f28d1e moved unittests of compilerInterfaces into compiler module itself 2021-10-29 23:46:51 +02:00
3da9404c2d removed memsizer arg from all builtin functions 2021-10-29 23:38:31 +02:00
4d5bd0fa32 simplify ZeroPage reserved locations handling a bit 2021-10-29 17:34:42 +02:00
1137da37c3 reshuffle ErrorReporter 2021-10-29 17:02:03 +02:00
495a18805c move asmgen test to codeGeneration module 2021-10-29 16:20:53 +02:00
a226b82d0b cleanup imports 2021-10-29 05:30:12 +02:00
0b5ddcdc9b split out the code generator into own project submodule 2021-10-29 05:00:30 +02:00
82da8f4946 adding tests to the new project's submodules 2021-10-29 03:36:42 +02:00
5ff481ce3c make sure tmp folders exist for unit tests 2021-10-29 03:04:16 +02:00
f21dcaa6fb split out the code optimizers into own project submodule 2021-10-29 02:42:10 +02:00
2c940de598 better name 2021-10-29 01:06:01 +02:00
ce75b776bb refactor loadAsmIncludeFile response 2021-10-29 01:01:24 +02:00
7d22b9b9f9 simplified name conflict check for sub params 2021-10-29 00:20:33 +02:00
6cb8b3b5cd removed unneeded scope param from lookup() 2021-10-29 00:01:28 +02:00
2bf4017f2b fix nested label lookups in anon scopes
fixed non-global qualified names lookup
2021-10-28 23:48:01 +02:00
08d2f8568b refactoring symbol lookups 2021-10-27 23:48:12 +02:00
ac5f45d2d4 fix nested label lookups in anon scopes (partly) 2021-10-27 02:41:24 +02:00
3cc7ad7d20 slightly improve error message for unknown module import 2021-10-27 00:38:36 +02:00
d4513364fb fix compiler crash when file on command line doesn't exist 2021-10-27 00:23:54 +02:00
9684f4e42a add unit tests for AnonScope refactoring, cleaned up imports 2021-10-27 00:05:46 +02:00
f4186981fd todo 2021-10-26 23:30:48 +02:00
141689e697 change many uses of .definingScope to just the parent node 2021-10-26 23:25:16 +02:00
743c8b44a2 AnonymousScope refactor: it's no longer a INameScope
because it doesn't contain scoped variables (these are moved to the subroutine's scope)
2021-10-26 23:01:51 +02:00
5e1459564a no longer take AddressOf a str-variable that is a subroutine's parameter with str type (it's just an address/uword already) 2021-10-25 23:49:01 +02:00
69a8813a3d first steps to add support for str parameter type 2021-10-24 20:57:10 +02:00
17175df835 more precise error messages checks 2021-10-24 19:14:46 +02:00
6b32535cb6 don't complain about uninitialized str var if it's not a var 2021-10-24 15:13:38 +02:00
7f8fe75ab2 version 7.1 2021-10-24 14:00:11 +02:00
2815a14bb5 (7.2) can now test for specific error messages, and specify to omit invoking assembler in tests 2021-10-22 01:25:26 +02:00
f4dfa60790 (7.2) tests for pass by ref parameters 2021-10-22 00:41:34 +02:00
35e88dd529 (7.2) correctly parse datatype of array parameters 2021-10-21 22:06:21 +02:00
4d5094a517 (7.2) cleanup Petscii converter errorhandling, add unit tests for error scenarios 2021-10-20 23:48:20 +02:00
dd5abae721 move testcase to proper location 2021-10-20 23:08:40 +02:00
8f2fb20934 Merge branch 'v7.1' into v7.2 2021-10-20 22:51:14 +02:00
44143f481a Merge branch 'v7.1' 2021-10-20 22:50:35 +02:00
440abf4998 fix test to recognise inserted return statements 2021-10-20 22:50:18 +02:00
3c10427e04 Merge branch 'v7.1' 2021-10-20 22:38:23 +02:00
74555a32ed Merge branch 'v7.1' into v7.2 2021-10-20 22:37:43 +02:00
85956b5828 code generator: add a return (RTS) to empty subroutines. Fixes #67 2021-10-20 22:36:13 +02:00
41e40cad03 optimizer bug: don't remove empty subroutine if it's referenced. Fixes #67 2021-10-20 22:25:10 +02:00
df2d5c6585 tests for callgraph and unused subroutine removal in optimizer 2021-10-20 22:24:10 +02:00
1a111b706e Merge branch 'v7.1' into v7.2 2021-10-19 23:59:31 +02:00
f696fce187 Merge branch 'v7.1' 2021-10-19 23:59:07 +02:00
82d3d81bb2 don't want to complicate things by introducing a boolean literal 2021-10-19 23:58:50 +02:00
4668932bac todo 2021-10-19 23:38:07 +02:00
e6c41eac93 Merge branch 'v7.1' into v7.2 2021-10-19 23:22:38 +02:00
f0cff661df Merge branch 'v7.1' 2021-10-19 23:21:44 +02:00
82d20dea39 a few comment and TODO cleanups.
remove remark about chars UBYTE type, kotlin's closest native type that can contain 0-255 is a short.
2021-10-19 23:20:34 +02:00
804bb06859 clarified isInRegularRAM() by making it an extension method on AssignTarget 2021-10-19 22:36:05 +02:00
5afa7e53f8 got rid of program arg for isInRegularRAM 2021-10-19 22:30:30 +02:00
7f15b7b716 remove unneeded check for duplicate module names as this is now caught by the logic in Program.addModule itself 2021-10-19 22:12:54 +02:00
552e0c2248 rename mainModule to toplevelModule.
failed module no longer retains in the Ast.
improved some tests on that.
2021-10-19 21:49:05 +02:00
e5b9e1f5e7 string object identity hashcode can be negative sometimes, so allow a '-' character. 2021-10-19 21:08:15 +02:00
502bf90007 comments 2021-10-19 01:12:28 +02:00
40bf117497 avoid crash when parser doesn't report an offending token for a parse error 2021-10-19 00:44:33 +02:00
4011dce31b added a few more tests for the file element of Position 2021-10-19 00:26:02 +02:00
9e881e32f8 version 7.1-beta2 2021-10-16 18:47:16 +02:00
14aad2358f version 7.2 started 2021-10-16 18:46:08 +02:00
637a8899c5 Merge pull request #65 from irmen/v7.1
more V7.1 updates
2021-10-16 17:51:56 +02:00
cf0e395921 got rid of SourceCode.pathString() and the 'need' to strip < and > 2021-10-16 17:15:22 +02:00
6ef438ce50 todo 2021-10-16 15:08:36 +02:00
46e4b977a4 another attempt to fix Windows path issues 2021-10-16 15:02:15 +02:00
9626c5dead attempt to fix Windows path issue with "library:" prefixes in AsmGen 2021-10-16 14:50:08 +02:00
aea364e43d paths are now always relative to the current directory. Fixes #64 2021-10-16 14:26:33 +02:00
06defd0cb0 paths are now always relative 2021-10-16 02:43:22 +02:00
0f80897c50 todo 2021-10-15 01:02:32 +02:00
57bb1c2c0d performance optimized checks against short ranges of values 2021-10-15 00:51:45 +02:00
7b35b414e8 tweak check of DataType against multiple values 2021-10-15 00:39:42 +02:00
761aac7a23 replace inferredType.istype() by infix form 2021-10-15 00:28:23 +02:00
15a02d7664 making InferredType easier to use 2021-10-15 00:18:13 +02:00
16ed68c1ec Module.name is now derived back from the source's origin string 2021-10-14 23:58:14 +02:00
e63cf660c6 petscii now use Result instead of Either 2021-10-13 23:22:46 +02:00
aaff484306 refactor executeImportDirective 2021-10-13 23:14:27 +02:00
3281d9a215 fix error when sourcepaths is empty 2021-10-13 23:08:51 +02:00
0fcd61e00f refactor tryGetModuleFromResource 2021-10-13 23:00:22 +02:00
c4523ea470 refactor tryGetModuleFromFile 2021-10-13 22:32:52 +02:00
0447b3e4cc remove testcase that attempted to check invalid %import syntax.
we only allow unquoted names, without filename suffix, in %import.
2021-10-13 22:10:35 +02:00
4d27c2901b fix weird error printing when doing %import textio.p8 2021-10-13 21:55:51 +02:00
855e18b31c fix SourceCode to properly set the sourceName of a resource or string as well 2021-10-13 21:46:38 +02:00
d790878af6 enabled test 2021-10-13 20:28:42 +02:00
85b244df2f remove remains of %target 2021-10-13 20:13:57 +02:00
6070afa6b6 cleanup SourceCode class 2021-10-13 19:16:01 +02:00
975594703d doc 2021-10-13 18:21:48 +02:00
6b8c3ef614 renamed command line option -libdirs to -srcdirs
this more clearly separates this meaning from the internal library modules
2021-10-13 18:16:51 +02:00
9b22f05381 7.1 beta 2021-10-13 01:36:20 +02:00
ca3a990f9e todo 2021-10-13 01:33:29 +02:00
557f4f689f doc 2021-10-13 00:50:54 +02:00
66574d058a renamed InferredType.typeOrElse to getOr()
this is closer to the convention of most functional return types
2021-10-13 00:21:38 +02:00
1c7c67060d better result and error handling for importModule() 2021-10-12 23:54:48 +02:00
9827ee97ad better returnvalue/errorhandling for Petscii encoding 2021-10-12 23:26:45 +02:00
71a9a84211 don't throw basic AstException but SyntaxError instead 2021-10-12 22:30:38 +02:00
367a2a4cee cleaner return type 2021-10-12 22:21:38 +02:00
4f7465ba44 better return types 2021-10-12 21:59:19 +02:00
f891fc698c switched to more featureful Result library 2021-10-12 21:35:27 +02:00
36bec62c9a Merge branch 'master' into v7.1 2021-10-12 20:28:44 +02:00
dd5a2c8315 get rid of automated CI builds for now
the tests that actually run the compiler + assember don't work there (for now)
2021-10-12 20:27:25 +02:00
56bff46481 Update gradle.yml 2021-10-12 18:19:07 +02:00
b83a0adb19 Update gradle.yml 2021-10-12 18:17:50 +02:00
92ffefe656 create github CI action to replace travis CI 2021-10-12 18:15:12 +02:00
51b2e41879 libs updated to maven 2021-10-12 03:33:52 +02:00
ef43bc9208 lib update 2021-10-12 02:33:34 +02:00
33733a4001 improve errorhandling 2021-10-12 01:45:32 +02:00
e5a1b37981 simplify 2021-10-12 01:22:17 +02:00
30aa72dc8e fix unittest and use kotlin.test method to test for exceptions 2021-10-11 21:22:06 +02:00
2c2d474059 fix crash when attempting to import non-existing module 2021-10-11 20:37:55 +02:00
c55ac0450f unified @embedded@ and library: into the latter 2021-10-11 19:22:56 +02:00
2d26b9c994 fixed module parent linking mistakes in unit tests: module's parent should always be the GlobalNamespace 2021-10-11 01:34:55 +02:00
f38fe092ee optimized imports 2021-10-11 00:22:04 +02:00
7a33eb163b also use output path when launching emulator, fixes #61 2021-10-11 00:19:48 +02:00
5db0408b9f syntactic sugar: turned some functions into read only properties 2021-10-11 00:05:51 +02:00
3557d38ce0 cleanup: fix spelling errors and some compiler warnings/suggestions 2021-10-10 23:35:02 +02:00
7de4e9e66a exclude some more build folders from the IDE 2021-10-10 23:04:31 +02:00
73838ccb8b ref github issue 2021-10-10 23:00:31 +02:00
0509de76d5 Merge pull request #53 from meisl/testability_steps_1_2_3_again
Implement plan for testability
2021-10-10 22:30:29 +02:00
f4b3d19059 fix merge conflict 2021-10-10 22:26:18 +02:00
f37fb82d53 Merge branch 'v7.1' into testability_steps_1_2_3_again
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/parser/ModuleParsing.kt
#	compilerAst/test/TestAntlrParser.kt
#	parser/antlr/Prog8ANTLR.g4
2021-10-10 22:20:08 +02:00
dbe98f3fa5 remove unittest of %target directive, which is removed in 7.1 2021-10-09 18:43:18 +02:00
371d4768e6 fix filename case issue 2021-10-09 17:59:40 +02:00
562d8386ec fix antlr generator settings 2021-10-09 16:57:56 +02:00
1625e4eb85 rename prog8Parser (generated java) to Prog8ANTLRParser 2021-10-09 16:56:39 +02:00
2365a076ac clean test.p8 2021-10-09 16:33:52 +02:00
9898791771 clean test.p8 2021-10-09 16:32:44 +02:00
a1c658274d Merge pull request #50 from meisl/v7.1
*/+ fix comments & test messages, *add CompilerDevelopment.md*
2021-10-09 16:27:14 +02:00
be9998b48b Merge branch 'irmen:v7.1' into v7.1 2021-10-09 14:48:19 +02:00
e8f308f654 a few more inlinings of trivial return values 2021-10-09 01:36:13 +02:00
07132a2c42 removed unreliable inlining of non-asmsub subroutines. Fixes #60 2021-10-08 23:09:38 +02:00
9c4582e283 optimized codegen of swap of two memread values with index on the same pointer; like swap(@(ptr+i1), @(ptr+i2)) 2021-10-08 18:19:16 +02:00
0204002d9b bugfix: non-existing asm function was sometimes called to swap floats 2021-10-08 04:14:43 +02:00
b3107cfad0 Merge branch 'master' into v7.1 2021-10-04 22:38:53 +02:00
91ae68c07e blinds effects done 2021-10-04 22:15:59 +02:00
06b3bf27b5 slightly improve sys.waitvsync() on c64 2021-10-03 21:35:12 +02:00
fbef63e150 moving to raster lines via irq 2021-09-30 23:45:45 +02:00
bb8ee9bb3e Merge branch 'master' into v7.1 2021-09-28 23:27:56 +02:00
25677a4126 remove unused var 2021-09-28 23:27:32 +02:00
3aeca0a770 Merge branch 'master' into v7.1 2021-09-28 23:15:06 +02:00
4bd4733e52 fix index calc bug in palette.set_color 2021-09-28 23:12:59 +02:00
9acec4d952 changed to fixed point math to always generate bars of 32 lines height 2021-09-28 23:12:16 +02:00
8388adcd1d changed to fixed point math to always generate bars of 32 lines height 2021-09-28 22:55:55 +02:00
5988ba76b5 test example for fixed float ranges 2021-09-12 19:10:50 +02:00
1a06e7a16e expand range expression in float array decls, fixes issue #55 2021-09-12 19:02:07 +02:00
7241cef7a5 fix char range in float-range test and exclude test.p8 example from tests 2021-09-12 18:59:53 +02:00
5145296486 fix test assertion for float ranges (and re-enable test) 2021-09-12 18:53:12 +02:00
2cbf2d2226 fix regression in imported module order (reversed)
this caused an error in determining the main module and correct compilation options
2021-09-12 18:16:24 +02:00
754664aefa correctly allow codegen to proceed for byte->word register assignment. Fixes crash #58 2021-09-06 23:22:43 +02:00
af99173cd7 range expressions are on integers only 2021-09-06 22:15:27 +02:00
fd1f30f92b removed %target directive (didn't add much, too confusing, only supported single target) 2021-09-04 15:01:16 +02:00
d9ab2f8b90 upgrade to kotlin 1.5.30 2021-09-03 23:00:00 +02:00
bd6c60cf8a * improve test method names in helpers_pathsTests by means of backtick syntax 2021-08-02 15:47:42 +02:00
f0c150d93b * improve test method names in TestProg8Parser by means of backtick syntax 2021-08-02 15:36:08 +02:00
c2986eaf47 * structure TestProg8Parser with @Nested 2021-08-02 14:52:46 +02:00
ef0c4797bb Merge remote-tracking branch 'remotes/origin/v7.1' into testability_steps_1_2_3_again 2021-08-02 10:26:48 +02:00
ac02a99934 * move ModuleImporter to prog8.compiler (package & module), together with its tests 2021-08-02 10:07:19 +02:00
fb67d1155f * structure TestCompilerOnImportsAndIncludes, add (@Disabled for now) test re %import with string arg 2021-08-02 08:57:09 +02:00
eb46852bb9 * restrict access to Program.modules, add tests 2021-08-02 08:15:20 +02:00
007d8d2811 * ModuleImporter tests: refactor, more precise assertions about the program's modules 2021-08-01 17:27:41 +02:00
ebe04fc114 * @Disable ModuleImporter test re importing a faulty module twice - no easy fix for this atm 2021-08-01 16:26:27 +02:00
d7dd7f70c0 * rename file ModuleParsing.kt to ModuleImporter.kt (nothing else, still in compilerAst) 2021-08-01 15:38:21 +02:00
f2cb89a128 - ModuleImporter: deduplicate code 2021-08-01 15:37:57 +02:00
b8fade23de * (first quick) fix: ModuleImporter should look in given "libdirs" (or better "srcdirs"?) for module file 2021-08-01 15:17:47 +02:00
3b97a17648 * *little bit* of cleanup in ModuleImporter - *only refactoring* 2021-08-01 11:18:45 +02:00
0d06e3ff22 */+ refactor tests of ModuleImporter, add some tests related to libdirs issue 2021-08-01 10:48:28 +02:00
c914f7bbcf + TestCompilerOptionLibdirs.kt: libdirs option doesn't seem to work 2021-08-01 10:16:37 +02:00
1b451180c1 * test helpers assumeXyz (helpers/paths.kt) return the resulting path (unless they fail, of course); test directories are checked automatically at init, so no sanityCheckDirectories is needed anymore 2021-07-31 14:44:02 +02:00
ed061b362b * #53 step6: move IStringEncoding to prog8.compiler (package as well as module) 2021-07-30 19:25:18 +02:00
e1026584c8 * split up test helpers into separate files, move mapCombinations(..) down to compilerAst/test/helpers since they're generic and don't depend on compiler 2021-07-30 18:37:12 +02:00
4c615e4fac * solve problem re shared test helpers: a) don't use the same file name (results in same JVM class name) & b) tell gradle about it (put them in extra dir(s) test/helpers/ and add this to test source set) 2021-07-30 17:19:44 +02:00
7c9d48833b Merge branch 'irmen:v7.1' into v7.1 2021-07-22 12:14:34 +02:00
b60b195aec update junit and hamcrest unittest lib versions 2021-07-20 22:42:27 +02:00
db76c8d7f4 -/* remove IStringEncoding as param in compilerAst, and all other uses that were only because of that.
For good measure we also turn on *all* compiler tests with examples (they do take some time).
Note that the total *mentions* of IStringEncoding in the entire project went down from ~50 to 6, only 3 of which are *actual uses* (the others are 2 imports and 1 supertype ref in ICompilationTarget : IStringEncoding)!
2021-07-17 22:08:17 +02:00
de92740e87 * simple refactoring: move IStringEncoding, IMemSizer and IBuiltinFunctions to files of their own, also ext method Number.toHex to file compilerAst/src/prog8/ast/Extensions.kt. Moved the other interfaces that are indeed closely related to Node to the top of AstToplevel.kt. *This is really ONLY moving text around*, so it's easier to find things. Nothing else. 2021-07-17 21:29:01 +02:00
522bf91c30 * refactor RangeExpr, step 2: make toConstantIntegerRange and size *extension methods* and move them to compiler/astprocessing/AstExtensions.kt (along with the simple helper makeRange). They are in fact *only* used from the compiler module - strong indication that they actually belong there. 2021-07-17 21:13:34 +02:00
48d3abc1fe * refactor RangeExpr, step 1: remove IStringEncoding as ctor arg and instead put it as arg to the two methods that actually depend on it: toConstantIntegerRange and size (as *it* calls the former) 2021-07-17 20:45:17 +02:00
3f6f25e06f * @Disable tests re unsolved #55, "float[] initializer with range and no explicit array size" 2021-07-17 17:12:16 +02:00
34ba07ee3b + expose #55: float[] initializer as range where no array size is stated 2021-07-17 16:30:16 +02:00
ac37319d20 Merge branch 'bug_asmbinary' into testability_steps_1_2_3_again 2021-07-17 15:08:32 +02:00
b2c6274f74 * fix #54 / step 3: avoid some (= not all) complaints re the .binary filename 64tass still had/has.
Actually, I don't quite understand why it still says "not the real name of the file". The 64tass docs say:
> -Wno-portable
>   Don't warn about source portability problems.
>   These cross platform development annoyances are checked for:
>   * Case insensitive use of file names or use of short names.
>   * Use of backslashes for path separation instead of forward slashes.
>   * Use of reserved characters in file names.
>   * Absolute paths
2021-07-17 13:03:05 +02:00
402884b5ce * fix #54 / step 2: the path stated with assembler directive .binary must be *relative to the .asm file*, not the working directory 2021-07-17 13:02:48 +02:00
23c99002c0 * fix #54 / step 1: relativize threw IllegalArgumentException if called on non-absolute path with absolute path as argument ("different type of path") 2021-07-17 13:02:35 +02:00
ee115b3337 + expose #54, %asmbinary when outputDir != workingDir; also: refactor compiler tests on examples and add test helpers 2021-07-17 13:02:20 +02:00
82f5a141ed * reintroduce the conversion of CharLiteral to UBYTE literals, but now *during AST preprocessing*, not in the parser 2021-07-11 22:03:32 +02:00
0567168ea9 + add AST node CharLiteral, *without* turning them into ubyte s. This breaks tests, particularly 3 in TestCompilerOnCharLit. I'm comitting this separately since the failure modes might be of interest (compiler says "internal error"). 2021-07-11 21:32:18 +02:00
c80a15846d * some more housekeeping re tests: gradle doesn't like .* imports for annotations, added @Disabled comments, made warnings go away 2021-07-11 19:04:53 +02:00
5e194536a8 * refactor compiler tests, again prog8test.helpers (TODO: remove duplication) 2021-07-11 18:18:27 +02:00
43c5ab8ecc * refactor compilerAst tests, intro prog8test.helpers, @Disable the 3 tests that will pass after subsequent steps of "the plan" 2021-07-11 17:32:29 +02:00
cd295228ef + TestCompilerOnImportsAndIncludes.kt: 2 tests, both passing (but see FIXME in asmIncludeFromSameFolder.p8) 2021-07-11 15:33:44 +02:00
6c42221620 * fix AstToSourceCode: missing semicolon in header and footer, missing "@" for strings with altEncoding 2021-07-11 14:28:09 +02:00
0d73a7cd07 + add TestAstToSourceCode.kt (all 8 new tests failing due to missing semicolon) 2021-07-11 14:11:32 +02:00
39d5b7edb0 + test examples for both platforms, cx16 and c64; test two more: tehtriz and textelite (the largest ones, 20KB / 36KB) 2021-07-10 21:41:51 +02:00
6fa50a699f + add two tests for parseModule with empty source text (from File and from String) 2021-07-10 21:03:40 +02:00
ddaef3e5d5 + add tests for SourceCode.fromResources; refactor tests 2021-07-10 20:55:23 +02:00
c3e9d4a9f8 * make resources available in compilerAst/test s; *you may have to re-import the gradle project into IDEA*
Note: these resources are NOT going into the production .jar
2021-07-10 20:50:07 +02:00
7530fb67c8 + add tests for inner nodes' positions; refactor tests 2021-07-10 10:10:41 +02:00
19bb56df47 * no more scattering magic "@embedded@" all over the place: add SourceCode.isFromResources, *change Module.source from type Path to type SourceCode* 2021-07-09 17:32:33 +02:00
b0073ac933 * used "@embedded@" convention instead of "<res:...>", put it into SourceCode 2021-07-09 16:28:04 +02:00
137a89da15 * fix (hack) .name, .source and .position of Modules from the parser (via temp. subclass ParsedModule)
The temporary subclass ParsedModule : Module is introduced to concentrate all the workaround stuff in one place *while still not changing any public signature* such as of the Module ctor.
The convention used to indicate stuff from resources is still "<res:...>" not "@embedded@"- *note that this is caught by 3 tests in compiler*
2021-07-09 15:52:03 +02:00
44da7a302f + temporarily hack together a module name inside Prog8Parser.parseModule, to make the current all-too-simple import resolution work 2021-07-09 14:01:07 +02:00
4096aae8d4 * SourceCode.toString() now states both, java class and .origin 2021-07-09 13:55:56 +02:00
d3e026d82a +/* non-unique module names: provide more info, add TODO 2021-07-09 13:44:44 +02:00
fa5ecd6495 * refactor ModuleImporter: throw the proper NoSuchFileException if import isn't found, return SourceCode? from both, tryGetModuleFromResource and tryGetModuleFromFile 2021-07-09 13:44:24 +02:00
af209ad50e + intro SourceCode, tying together source code text with its *origin*; Prog8Parser now only accepts this 2021-07-09 13:24:05 +02:00
7b89228fa7 + add TODOs re ICompilationTarget 2021-07-09 13:14:02 +02:00
d31a88206c * importModule(Path): make tests pass (TODO: importLibraryModule with non-existent path) 2021-07-09 13:13:42 +02:00
cd4ed8765b + add tests for importModule(Path) with invalid path (non-existent or directory) - *failing* 2021-07-09 13:10:42 +02:00
b6f780d70d * ModuleImporter: make tests pass 2021-07-09 13:02:30 +02:00
b071a58ca7 + add tests - 4 failing in TestModuleImporter 2021-07-09 12:51:07 +02:00
ce554f7718 * rename test file 2021-07-09 12:49:55 +02:00
99b1cec2e1 */+ move ParsingFailedError to Prog8Parser.kt, intro ParseError (soon to replace ParsingFailedError), start testing proper error location info 2021-07-09 12:49:30 +02:00
46911a8905 + temporarily add PetsciiEncoding (and Petscii.kt copied from compiler) to parser; .linkParents for child nodes of Module 2021-07-09 12:31:46 +02:00
4eb61529f6 */+ rename prog8Parser (generated java) to Prog8ANTLRParser; add Kotlin class Prog8Parser to interface with it 2021-07-09 12:24:17 +02:00
81abf29bec Merge branch 'irmen:v7.1' into v7.1 2021-07-09 11:03:31 +02:00
85897ef8cd launch box16 emulator with the vice monlist file that contains symbols+breakpoints 2021-07-06 22:27:47 +02:00
b824c0b125 kotlin code style setting 2021-07-05 23:41:40 +02:00
6367c6d116 add support for second alternative emulator (box16 in case of cx16 target) 2021-07-05 22:47:51 +02:00
a7736d88a9 got rid of Module.isLibraryModule variable, is now function that derives it from source path 2021-07-04 15:44:25 +02:00
049dbf5a78 improve compiler error when defining duplicate block names 2021-07-04 15:14:39 +02:00
4ac92caeb5 Update CompilerDevelopment.md 2021-07-03 15:11:34 +02:00
7af3da2a97 Merge branch 'irmen:v7.1' into v7.1 2021-07-03 15:08:29 +02:00
95a62fcdd1 tidy up todo doc 2021-07-02 21:47:27 +02:00
7872d20554 rename spelling mistake 2021-07-02 20:58:17 +02:00
a598eb7e98 + add mention of ParseError : ParsingFailedError - particularly for testability this is something that needs to be done 2021-07-02 18:42:38 +02:00
c786acc39b + CompilerDevelopment.md, outlining what to do to improve testability (atm only for the parsing stage) 2021-07-02 15:41:38 +02:00
07d8052a57 * fix comments: no more problem with exitProcess 2021-07-02 13:28:19 +02:00
db9edb477e * less confusing assertion messages (seemingly contradictory in case of failure) 2021-07-02 12:38:24 +02:00
9bd3a6758a improve testability: use error returnvalues instead of using exitProcess() 2021-07-02 00:11:21 +02:00
2cb1560bbd Merge pull request #49 from meisl/master
A few automatic tests with examples, *of the whole process*...
2021-06-30 23:30:10 +02:00
006059438f + same warning on the other "TestCompilerXyz" file 2021-06-28 18:49:01 +02:00
84ea3b9788 + compiler/test/TestCompilerOnExamples.kt: *not actually unit tests - just a kludge!* (but better than nothing) 2021-06-28 18:42:05 +02:00
b667abde10 + compiler/test/TestCompilerOnCharLit.kt 2021-06-28 18:24:35 +02:00
aa10997171 upgrade to kotlin 1.5.20 2021-06-25 19:38:44 +02:00
7880ac1909 wording and version 2021-06-24 21:34:11 +02:00
f53848b4b9 wording and version 2021-06-24 21:25:35 +02:00
82f2f38680 Merge pull request #48 from meisl/issue40(EOF,EOL)
* fix 47, add tests
2021-06-19 01:55:44 +02:00
dcc2549574 * fix 47, add tests 2021-06-18 21:55:03 +02:00
cfe4753913 Merge pull request #45 from meisl/issue40(EOF,EOL)
Issue #40, EOF/EOL
2021-06-18 20:22:59 +02:00
fcb1a7e4d4 * #40: fix grammar rules module and block s.t. we don't need a "synthesized double EOF" (behavior remains exactly the same) 2021-06-14 22:17:30 +02:00
ce76a7dfa5 * #40: fix grammar wrt line endings - tests pass 2021-06-14 22:04:22 +02:00
7c1de81861 * #40: fix mixed line endings test, now intentionally failing (!): also test sole \r AND do not allow any recovery, neither from parser not lexer. 2021-06-14 22:02:26 +02:00
eddad20acc Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 22:56:24 +02:00
7daad57862 + #40: test for mixed (Unix/Win/Mac) line endings - *TODO: test doesn't actually fail with old grammar, but a built jar does - WHY?!* 2021-06-13 22:49:54 +02:00
1468049fe9 + #40: test that (module-level) blocks *before the last* still must have a newline after their closing } 2021-06-13 22:43:27 +02:00
3b91e59a79 * #40: refactor tests 2021-06-13 20:28:01 +02:00
3496a30528 * #40: put back in fix for EOL-after-block - tests pass 2021-06-13 20:10:35 +02:00
32bad5df15 +/* #40: add tests; temporarily undo fix for EOL-after-block so we can see that tests actually fail 2021-06-13 20:08:50 +02:00
3f58eca1be updated gradle scripts (fixed warnings), updated some library dependencies 2021-06-13 18:10:07 +02:00
2350843d1d Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 16:06:50 +02:00
a2588a178c added some simple unit tests to the ast parser 2021-06-13 14:59:57 +02:00
e5292696c4 - #40 grammar: remove obsolete note about line endings 2021-06-13 14:38:25 +02:00
34b2a65ccb Merge branch 'irmen:master' into issue40(EOF,EOL) 2021-06-12 20:29:37 +02:00
3aa3659bc7 * #40 grammar: handle different EOLs (Win, Unix, Mac) purely in grammar 2021-06-12 20:24:15 +02:00
b8117394c0 * #40 grammar: don't require EOL after blocks, so .p8 files need not end with that 2021-06-12 17:52:44 +02:00
fd2bbd2b59 no longer allow subroutine name same as its block name due to asm symbol scoping issues 2021-06-12 17:31:09 +02:00
127c470746 add some explanation about Cx16 v38 - v39 issue 2021-06-12 15:48:04 +02:00
f2844bdf1a fix crash when using labels in pointerexpression lab+index 2021-06-10 00:44:12 +02:00
c5bfef4264 slight improvement on scope doc, added doc example for %asminclude/%asmbinary 2021-06-09 23:46:07 +02:00
73863acc12 version bump 2021-06-06 10:50:05 +02:00
19e99204b9 fix asm symbol name scoping bug and add unit tests for this 2021-06-04 22:42:28 +02:00
13f5b94c3e Clarified instructions of how to obtain the compiler. Fixed sphinx css config issue. 2021-06-03 21:21:44 +02:00
3a2498401d working on unit tests for symbol scope bug 2021-06-03 21:21:38 +02:00
53b20ba625 name 2021-06-01 22:22:58 +02:00
e7f6f0950f identified asm symbol name scoping bugs 2021-06-01 22:21:50 +02:00
9fbe1b38a5 fix old block syntax in ast print routine 2021-06-01 22:08:23 +02:00
078485134d split up unittests files 2021-06-01 22:07:39 +02:00
67b1766e32 don't use ./ prefix for %asmbinary paths 2021-06-01 19:30:53 +02:00
d4b69ac79c improved repeat counter vars allocation (re-use var if possible) 2021-05-30 15:30:34 +02:00
e61a2d7083 slightly optimized repeat loop asmgen 2021-05-30 13:10:05 +02:00
c03f6604af added free words counting method to zeropage 2021-05-30 00:55:11 +02:00
572bb38ddb update to kotlin 1.5.10 2021-05-29 15:25:17 +02:00
42c5c0cb9f start of cx16 colorbars example 2021-05-26 22:13:23 +02:00
e145d2255e added palette.set_all_black() and set_all_white() 2021-05-26 21:33:18 +02:00
442fa07dd4 relax name conflict rule regarding block names vs subroutine params 2021-05-26 21:32:54 +02:00
31ae9e1243 refactor repeat loop counter var creation into single routine 2021-05-22 13:01:51 +02:00
d7f83f8df2 version bump 2021-05-20 23:38:41 +02:00
29e2d4e0c8 give error when passing invalid command line option 2021-05-20 23:34:20 +02:00
2732d2c844 exclude d64 files 2021-05-19 18:49:38 +02:00
c4a037b277 added '@shared' to syntax files 2021-05-19 18:48:18 +02:00
0e614ad6fc added @shared flag to vardecl to mark variable as shared with assembly code elsewhere, to not have it optimized away 2021-05-19 01:19:25 +02:00
ca1a8cd617 improve doc about string (im)mutability 2021-05-19 00:15:17 +02:00
ba96a637be remove strdedup compiler argument again
(string deduplication is the default again but only for known-const strings, i.e. string literals)
2021-05-18 23:52:43 +02:00
c2cac772e3 validate string interning 2021-05-18 23:37:52 +02:00
6b7216f4ec todo 2021-05-17 19:00:20 +02:00
e4fb5946dd optimize cx16 sys.wait and sys.waitvsync to use WAI instruction 2021-05-17 18:44:42 +02:00
ca61248861 printing 2-letter strings is now only optimized into direct CHROUT if it's a const string literal 2021-05-16 15:00:40 +02:00
68d7b4649e label and directive location docs 2021-05-16 12:32:08 +02:00
0416aacbbd fix %asminclude by removing scopelabel argument and improving docs to remove false promise about labels 2021-05-16 00:14:57 +02:00
bc731e6f8e fix compiler crash when taking address of label 2021-05-16 00:07:48 +02:00
ae5d7705bb allow correct parsing of source files that don't end in a EOL character. Fixes #40 2021-05-14 17:14:44 +02:00
b9bd541532 restored optimization of printing short strings into just CHROUT
but added comment about known-constness still to be resolved
2021-05-13 01:46:43 +02:00
83639c2535 code style 2021-05-13 01:00:19 +02:00
25d80f4df1 added compiler option to choose string literal deduplication yes/no -- default changed to NO 2021-05-13 00:35:22 +02:00
74f918d911 fix crashes for string encoding errors: give normal compiler error instead 2021-05-11 21:33:11 +02:00
a20efa56eb print unmappable character in escaped form in errormessage 2021-05-11 18:09:09 +02:00
f4d83075be Merge pull request #35 from meisl/master
Notepad++ syntax-file: add notes re update / alt installation
2021-05-07 21:44:58 +02:00
254592c383 Merge pull request #36 from meisl/pull36
docs: fix typo
2021-05-07 20:04:00 +02:00
ee23ac0537 * docs: fix typo 2021-05-07 15:28:22 +02:00
a48cf0bb24 + #23 Notepad++ syntax-file: add notes re update / alt installation 2021-05-07 15:12:01 +02:00
dae59238cd fix array type checking crash when attempting to use str literal to initialize a byte array.
Fixes #34
2021-05-07 00:04:29 +02:00
8736da1a21 strings of 1 and 2 length no longer optimized into one call to CHROUT - also upgrade to kotlin 1.5.0 2021-05-06 23:46:18 +02:00
09a1de69e7 Merge pull request #33 from meisl/master
+ docs: add missing word
2021-05-06 23:45:44 +02:00
63d67bc6cb + docs: add missing word 2021-05-06 15:49:58 +02:00
7099245204 Notepad++ syntax file contributor added 2021-05-05 00:39:19 +02:00
4d097d2139 Merge pull request #32 from meisl/master
syntax file for Notepad++

thanks for your contribution!!
2021-05-05 00:33:06 +02:00
6485bf9ad9 +/- #23 add test file and screenshot; fix: remove if/else as "Folding in code 2", just keywords 2021-05-04 21:55:12 +02:00
b7c5b1bfc7 * #23 rename to .md for nicer link to homepage 2021-05-04 21:34:04 +02:00
2b7546e827 + #23 syntax file for Notepad++ 2021-05-04 21:30:53 +02:00
3549ccf4b3 software license 2021-05-02 15:31:14 +02:00
e2f5752d9a add f_open_w, f_write, f_close_w to diskio to be able to save parts of memory sequentially 2021-05-01 19:13:56 +02:00
1a59019fc8 add generic error in diskio.status() if drive status can't be read 2021-05-01 15:39:39 +02:00
7bac7bdc3e more precise 2021-05-01 13:39:02 +02:00
19fe58dbac fix regression bug that left variables uninitialized 2021-05-01 01:35:03 +02:00
0a5b30e21c added fast code for x*640 2021-04-30 22:30:21 +02:00
664818fd29 try fixing a weird problem with pointervar[idx] -> memread rewriting
this was introduced in the removal of structs somehow
2021-04-30 01:34:03 +02:00
d5214e2505 fix import paths 2021-04-30 00:16:36 +02:00
d906fcea0e refactor some type checks 2021-04-30 00:09:15 +02:00
29c8e8b740 doc 2021-04-29 19:57:14 +02:00
71fec4c555 added a few more simple special codegen segements for the logic operators on a memmory-read 2021-04-29 19:38:42 +02:00
5ee36c897d todo 2021-04-29 00:57:32 +02:00
4aba0c7405 unused variables are removed more aggressively (no longer checking asm blocks for their names) 2021-04-29 00:48:16 +02:00
ed7479c854 version 7 due to removal of structs and v39 cx16 support changes 2021-04-29 00:15:54 +02:00
8d3d5f726a removed Datatype.STRUCT 2021-04-29 00:13:17 +02:00
a9a7068818 removed support for structs. It was too much hassle and complexity and subtle bugs. 2021-04-29 00:01:20 +02:00
1bde7c7718 ver 2021-04-28 20:05:56 +02:00
17068130bb removed PROG8_LIBDIR env variables and replaced with -libdirs command line option 2021-04-28 20:04:23 +02:00
81a91d62cb improved horizontal_line in highres 4c 2021-04-28 02:55:49 +02:00
2575263438 optimized gfx2.plot() for hires-4c 2021-04-28 02:49:25 +02:00
7f0e25cb50 optimized gfx2.plot() for hires-monochrome 2021-04-28 02:32:11 +02:00
a1e4e9c50f optimized gfx2.plot() for lores-256c 2021-04-28 02:22:21 +02:00
98eff2701b optimized gfx2.plot() for lores-monochrome 2021-04-28 02:15:07 +02:00
8b84f87217 removed fastrnd8() because it was hilariously bad, just use rnd() 2021-04-28 01:53:12 +02:00
306a1b7bc2 optimized gfx2.vertical_line for hires monochrome mode 2021-04-28 01:19:10 +02:00
481214c46e optimized gfx2.vertical_line for lores monochrome mode 2021-04-28 01:02:29 +02:00
a5961cbeab optimized gfx2.vertical_line for highres 4c mode 2021-04-28 00:29:21 +02:00
3bf335e0a0 todo 2021-04-27 23:13:46 +02:00
68f696d165 added 'callrom' builtin function (for cx16 target) that calls a routine in banked ROM 2021-04-25 18:04:56 +02:00
1170aed026 added 'callfar' builtin function (for cx16 target) that uses jsrfar to call a routine in banked RAM 2021-04-25 17:47:13 +02:00
bf1b2066b6 fix crashes in peekw() and pokew() 2021-04-22 18:26:46 +02:00
4c080afb76 added compiler check against impossible for loop range (unsigned downto exactly 0 with non-const startvalue and step != -1) 2021-04-21 23:03:29 +02:00
ee1c43ca91 improved scanning for return statement in routines that should return a value. 2021-04-21 20:31:29 +02:00
1c2e6f9e4c lower() and upper() now also return the lenght of the processed string. 2021-04-21 20:21:58 +02:00
dd379430d9 added docs on flexible string character mapping to petscii 2021-04-20 01:22:49 +02:00
42033ebd35 added petscii mappings for ^, _, \, {, } and | 2021-04-19 02:18:55 +02:00
a086d6e009 allow labels also in blocks instead of only in subroutines 2021-04-18 23:03:18 +02:00
c70bbdab26 fixed missing type checking in vardecl initializer values. Fixes #29
Also fix wrong assert of 0 const check in assembly gen for if-statement comparisons.
2021-04-18 22:46:21 +02:00
3d956ef554 fix wrong values for register used in array indexing expressions
added the L/H byte parts of the cx16 virtual registers
2021-04-18 13:53:02 +02:00
329f491c30 fix compiler crash with scoped const vardecls 2021-04-18 01:56:26 +02:00
e93701f50e fix compiler error when initializing var with memory(...) in block scope instead of subroutine 2021-04-17 15:49:41 +02:00
e680de05ea workaround for the joystick_get() irq problem 2021-04-15 22:56:52 +02:00
56fec674c5 actually not simplifying if-code generation, leads to larger code at the moment 2021-04-13 00:03:22 +02:00
54d92a027a fix problems with moving vardecls from inner scope to subroutine scope 2021-04-12 22:53:25 +02:00
319ac3a641 preparing optimizations for if statements 2021-04-12 03:34:58 +02:00
0a03c46351 preparing optimization plan for if statements 2021-04-12 02:37:15 +02:00
ae1b62e147 optimized integer comparison expressions some more 2021-04-12 01:23:17 +02:00
8d567f6b06 added cx16.joystick_get2() for convenience api 2021-04-12 01:07:46 +02:00
b1ef09675b fix compiler crash for some struct/array initialization assignment literals containing not just numbers 2021-04-10 00:28:32 +02:00
2b7b925090 codegen now uses correct machine target's string encoder/decoder. Encoding more robust by checking upper case mapping if lowercase mapping fails. 2021-04-09 23:33:32 +02:00
e0454e95db warn about for-loop wrapped iteration if loop range is inverted from normal 2021-04-08 22:54:47 +02:00
91e421d961 optimize x % p where p=power-of-2, into just x & (p-1) 2021-04-08 22:21:16 +02:00
c853afe769 fix compiler crash due to certain redundant typecast expressions 2021-04-08 19:45:44 +02:00
1a64cb38d5 fix compiler crash with assigning certain array values as vardecl initializer 2021-04-08 19:21:17 +02:00
ccebd22856 callgraph: mark start() also in use 2021-04-08 02:43:59 +02:00
a1f3b82333 vtui update 2021-04-08 01:36:25 +02:00
3dda29781e changed MEMTOP2 into cx16.numbanks() to query the number of RAM banks installed 2021-04-08 01:05:38 +02:00
a9d297ee31 fix inlining of sub with var that has default initialization 2021-04-08 00:35:02 +02:00
e5ff61f201 allow inlining of subroutines with parameters, and fix inlining of subroutines with variables 2021-04-07 23:38:25 +02:00
d116eb7655 paranoid, be sure to not kill carry 2021-04-06 23:55:20 +02:00
bc726c6334 optimized slow evaluation of byte-to-wordarray assignment 2021-04-06 22:50:16 +02:00
123473dfc8 cleanup 2021-04-06 00:16:29 +02:00
d9eccd4fba set correct rom banks when using floats 2021-04-05 23:21:07 +02:00
5b890847e5 make sure BASIC rom is banked in again when program exits 2021-04-05 23:12:10 +02:00
64c85b9617 fix cx16 rom v39 float changes 2021-04-05 22:54:40 +02:00
3e3b0bcd8b callgraph improved unused node checking 2021-04-05 20:45:18 +02:00
4c1eb1b12a callgraph 2021-04-05 20:32:30 +02:00
530d03d284 callgraph 2021-04-05 18:50:46 +02:00
619fa9b65e callgraph 2021-04-05 18:03:36 +02:00
0032235933 tweak to fix for windows line ending (\r\n) parse errors 2021-04-05 01:49:52 +02:00
61d1f1ea87 oops 2021-04-05 01:18:22 +02:00
238d27acdc more pleasing bob image and pattern 2021-04-05 01:14:55 +02:00
2f62271453 callgraph 2021-04-05 00:55:27 +02:00
75d5117a2d fix struct flattening parent node mismatch 2021-04-05 00:30:42 +02:00
b4700af2f5 fix windows line ending (\r\n) parse errors 2021-04-05 00:12:04 +02:00
374e2b311d refactoring unused code removal and noModification 2021-04-04 16:36:33 +02:00
49036abbaf docs 2021-04-04 12:55:29 +02:00
38ccbac97c stop after a couple of parse errors (it's not useful to continue for long if there are parse errors) 2021-04-04 12:29:56 +02:00
6b4896b8f5 doc 2021-04-02 21:28:23 +02:00
d582d1cc42 fix inlining subroutines multiple times 2021-04-02 21:23:40 +02:00
9e2b8a2aa9 fix ast node duplication/reference bug in certain optimizers 2021-04-02 19:01:46 +02:00
6fdc733941 inlining subroutines that contain variable declarations is now possible (gives a warning though) 2021-04-02 18:30:32 +02:00
422b390c48 fix ast node duplication/reference bug in certain optimizers 2021-04-02 16:56:52 +02:00
67a9d1285c some words about how the X register can't or can be used 2021-04-02 00:19:46 +02:00
8e26e38ecc fix RTS-issue with inlined return statement 2021-04-01 23:30:19 +02:00
02e12d8575 improvements for inlined subroutines: fix identifier scoping 2021-04-01 23:16:04 +02:00
fe2954ce08 todo 2021-04-01 22:10:04 +02:00
1fe4439395 fixed wrong return value when calling other subroutines in the return expression 2021-04-01 21:56:24 +02:00
2ff04d2abd cleanup 2021-04-01 19:10:55 +02:00
3f30d3aa89 added sys.waitrastborder() for c64 2021-04-01 18:53:16 +02:00
129e17b33a added sys.waitvsync() + missing documentation 2021-04-01 18:31:33 +02:00
bf2d8c3f4b update kotlin plugin to 1.4.32 2021-03-31 20:52:05 +02:00
b29f04ce01 fix unittest 2021-03-31 20:49:35 +02:00
d185ebad48 Merge pull request #27 from Elektron72/cbm-package
Move code used for all Commodore systems to new package
2021-03-31 20:34:58 +02:00
605df7c91c Move code used for all CBM systems to new package
AssemblyProgram.kt and Petscii.kt are not only used for the Commodore
64; they are also used for the Commander X16, and will likely be used
for any future Commodore systems added to Prog8. Therefore, they should
be moved to a new package containing functionality shared between these
systems.
2021-03-29 17:21:48 -04:00
ec60cad8bb commander-x16 prototype board #2 (rom v39+) address changes 2021-03-27 22:20:46 +01:00
6aa0f5a392 small optimization 2021-03-27 15:45:30 +01:00
4cae2c56ec implemented last remaining codegen for word-byte division and remainders. 2021-03-25 22:03:36 +01:00
d840975054 remove unreached error checks 2021-03-25 21:47:05 +01:00
1b14da6c03 compiler warning instead of crash when attempting to assign invalid array value to other array 2021-03-24 22:01:22 +01:00
292640b17a asmgen: string values cannot be typecasted 2021-03-24 21:49:33 +01:00
112a7b09f2 added codegen for expression that needs the status-flag register result as a value on the stack 2021-03-24 21:42:27 +01:00
863ec9ce8a Merge pull request #26 from Elektron72/vim-syntax
Add support for built-in functions to Vim syntax file (and other fixes)
2021-03-24 20:50:53 +01:00
2eb346a205 Add support for built-ins to Vim syntax file
This commit adds support for highlighting built-in functions and
variables to the Vim syntax file.
2021-03-23 19:53:20 -04:00
8092355acb Add syntax sync to Vim syntax file
This will make the highlighting slightly slower, but will fix issues
with assembly not being highlighted properly.
2021-03-23 19:41:34 -04:00
e7ef2ed31b todo 2021-03-23 23:48:53 +01:00
af4de6d2fc replacing complex array indexer expressions moved to BeforeAsmGeneration + use cx16 virtualregister instead of adding temp variables for this 2021-03-23 23:44:14 +01:00
69f73dd779 Add void operator to Vim syntax file 2021-03-23 18:12:52 -04:00
9706b46012 credits 2021-03-23 02:50:16 +01:00
6d75dd3bb8 Merge pull request #25 from Elektron72/vim-syntax
Add Vim syntax highlighting file
2021-03-23 01:29:57 +01:00
bd295ffc99 array indexer complexity is now dealt with in the asm-generator only 2021-03-22 19:40:57 +01:00
07ce3e3c9d Add Vim syntax highlighting file
The readme file in syntax-files/Vim/ was also modified to give simple
installation instructions.
2021-03-22 12:13:20 -04:00
cbc3e37a89 stuff 2021-03-22 02:29:59 +01:00
3626828ceb decided 2021-03-22 01:45:19 +01:00
24b77fb5a5 comments. 2021-03-21 21:10:29 +01:00
1505fe686a updated vtui example 2021-03-21 20:40:35 +01:00
0991131fa8 don't stript unused asmsub definitions 2021-03-21 19:55:21 +01:00
2e928bd3c2 fix compiler crash for certain str argument to asm functions 2021-03-21 18:39:39 +01:00
ca868ae19e added cx16.vload() (like the VLOAD basic instruction) 2021-03-20 02:39:53 +01:00
3e286dd14c move test 2021-03-18 19:34:54 +01:00
11247d52b1 fix bugs in word <= and >= comparisons 2021-03-18 19:20:48 +01:00
1dbc902513 fix bugs in uword <= and >= comparisons 2021-03-18 18:41:41 +01:00
330e691b78 wip 2021-03-18 02:43:08 +01:00
6780d4f562 fix bug in uword > comparison 2021-03-18 02:21:21 +01:00
b30b8b7368 fix bug in float < and > comparisons 2021-03-18 01:41:54 +01:00
3df182b8c3 created extensive comparison test suite 2021-03-18 00:50:13 +01:00
7f21d89fea moved test programs to test folder in compiler module 2021-03-17 20:15:16 +01:00
2b267b4ba1 IDE syntax 2021-03-17 19:36:37 +01:00
ef64881528 busy creating extensive comparison test suite 2021-03-17 19:35:22 +01:00
9a6bd760bd fixed issues in uword '>' 2021-03-16 23:40:32 +01:00
00b9766aea fixed issues in word '>' 2021-03-16 23:22:58 +01:00
6381d2b6ac improve word '<', word (u)word '<=' , uword '>=' codegen 2021-03-16 18:15:47 +01:00
d2ab5f230d example TODOs 2021-03-16 01:09:25 +01:00
824b41d457 improve word '>' and '>=' codegen 2021-03-16 00:48:03 +01:00
b5523c7077 don't optimize with inlining too aggressively (code bloat) 2021-03-16 00:33:15 +01:00
eb3594b18c revert to just using comparison expressions in graphics code (we're optimizing these now!) 2021-03-16 00:11:55 +01:00
852d85d010 improve uword '<' and '>' codegen 2021-03-16 00:03:51 +01:00
5e0aef04fe improve (u)byte '>=' codegen 2021-03-15 23:20:16 +01:00
a00c693f93 improve (u)byte '<=' codegen 2021-03-15 23:17:04 +01:00
c943da1448 improve ubyte '<' and '>' codegen 2021-03-15 23:12:52 +01:00
b630fae580 refactor byte '==', '!=', '<' and '>' codegen 2 2021-03-15 23:08:30 +01:00
38e40084f1 refactor byte '==', '!=', '<' and '>' codegen 2021-03-15 22:47:18 +01:00
bf23ad78e6 improve byte '<' and '>' codegen 2021-03-15 22:26:00 +01:00
ded1d19737 improve '==' and '!=' codegen 2021-03-15 19:29:32 +01:00
496a3b0d2c todo 2021-03-15 18:56:25 +01:00
6922333755 add a cmp(x,y) function that returns no value but only sets the status bits based off the comparison (can be used with a conditional jump afterwards) 2021-03-13 15:11:22 +01:00
a00c39e9cf compiler error instead of crash when using functioncall without returnvalue 2021-03-12 19:31:04 +01:00
1c1da8e38e additional optimization to the bresenham line routines 2021-03-10 18:49:40 +01:00
50a306f492 line drawing fixes 2021-03-09 22:11:30 +01:00
6995ee2d17 fix cx16 bresenham line inaccuracy 2021-03-09 22:04:19 +01:00
6c60ea9cac allocate even more c64 zeropage locations for floats 2021-03-09 21:47:36 +01:00
2431ed811a don't remove typecasts in asmsub argument lists 2021-03-09 21:29:48 +01:00
6bd205c02a fix c64 bresenham line inaccuracy 2021-03-09 21:07:55 +01:00
62ec77e148 ver 2021-03-08 23:35:52 +01:00
9120e1de88 fix ubyte/uword to float conversion crashes on Commander X16 2021-03-08 23:21:52 +01:00
60e169bd87 added optimized integer square (x*x) routine 2021-03-08 23:08:47 +01:00
e4bca5fe47 version 2021-03-06 23:07:30 +01:00
a1729b65ab fix min(), max(), sum(), abs() 2021-03-06 22:57:22 +01:00
2950d26c8e array and struct value assignments now via memcopy instead of assignment per element 2021-03-06 22:10:03 +01:00
4f8d4a9585 use memcopy to assign arrays 2021-03-06 19:01:16 +01:00
d787795759 simplified 2021-03-06 15:43:23 +01:00
cf74e73e27 IDEA syntax colors 2021-03-06 15:23:58 +01:00
2770254fd9 removed inline assembly from bobs demo 2021-03-06 14:31:26 +01:00
de04bd8cfa added more convenient number-to-string functions to conv library 2021-03-06 13:47:27 +01:00
076a547f91 added more convenient number-to-string functions to conv library 2021-03-06 13:34:57 +01:00
dffd0a2706 added fastrnd8() with the old rnd() generator code in it, new code for rnd() uses the much better rndw() generator now. 2021-03-05 22:49:14 +01:00
6c66f86103 todo 2021-03-05 21:07:35 +01:00
26502c949a add unlimited bobs example 2021-03-05 19:05:13 +01:00
8dfe510883 avoid compiler crash when evaluating const expressions fails due to things like integer out of bounds 2021-03-04 01:32:02 +01:00
96ba9f5902 spelling correction 2021-03-04 01:31:29 +01:00
3a6ba0ab71 added 'kefrenbars' example 2021-03-03 01:09:18 +01:00
32d894d6b6 optimized repeat loop for word counts 2021-02-28 21:22:46 +01:00
543efa4299 attempt 2 at optimizing repeats 2021-02-28 21:02:17 +01:00
eba0708099 Revert "optimized repeat loop for word counts"
This reverts commit 51e6bf0d
2021-02-28 20:29:28 +01:00
51e6bf0d45 optimized repeat loop for word counts 2021-02-28 17:34:18 +01:00
07b5c44a54 preparing to optimize 16 bit repeat loop 2021-02-28 17:13:15 +01:00
9fe32c1c34 codegen uses 'bra' on 65c02 instead of 'jmp' 2021-02-28 16:46:08 +01:00
0e0278c84a for loops now use 'bra' if available 2021-02-28 16:35:59 +01:00
dea775a9cd package refactor 2021-02-28 16:29:15 +01:00
7e3e18a5c7 deal with 'bra' better on 65c02 2021-02-28 16:20:03 +01:00
8e3ebc84f0 readme 2021-02-28 15:40:04 +01:00
337 changed files with 24063 additions and 24302 deletions

3
.gitignore vendored
View File

@ -26,8 +26,9 @@ parser.out
parsetab.py
.pytest_cache/
.attach_pid*
compiler/lib/
.gradle
/prog8compiler.jar
sd*.img
*.d64

8
.idea/codeInsightSettings.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaProjectCodeInsightSettings">
<excluded-names>
<name>kotlin.Result</name>
</excluded-names>
</component>
</project>

10
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

7
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -7,6 +7,7 @@
<language isEnabled="false" name="Groovy" />
</Languages>
</inspection_tool>
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-4.9-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

19
.idea/libraries/antlr_antlr4.xml generated Normal file
View File

@ -0,0 +1,19 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.9.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.2/antlr4-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.2/antlr4-runtime-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3/ST4-4.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-runtime-4.9">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="dbus-java-3.2.4">
<CLASSES>
<root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,25 @@
<component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/glassfish_javax_json.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="glassfish.javax.json" type="repository">
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,22 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,24 @@
<component name="libraryTable">
<library name="io.kotest.property.jvm" type="repository">
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,56 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="javax.json-api-1.1.4">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.3/kotlinx-cli-jvm-0.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,15 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.12/kotlin-result-jvm-1.1.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.10/kotlin-stdlib-jdk8-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.10/kotlin-stdlib-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.10/kotlin-stdlib-jdk7-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.10/kotlin-stdlib-common-1.5.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="slf4j-api-1.7.30">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

11
.idea/libraries/slf4j_simple.xml generated Normal file
View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:1.7.30" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

13
.idea/libraries/takes.xml generated Normal file
View File

@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="takes" type="repository">
<properties maven-id="org.takes:takes:1.19" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.19/takes-1.19.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.42/cactoos-0.42.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,12 +0,0 @@
<component name="libraryTable">
<library name="takes-http">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="unittest-libs">
<CLASSES>
<root url="file://$PROJECT_DIR$/compiler/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
</library>
</component>

7
.idea/misc.xml generated
View File

@ -4,7 +4,7 @@
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" />
<option name="autoGen" value="true" />
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
<option name="libDir" value="" />
@ -16,7 +16,10 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

3
.idea/modules.xml generated
View File

@ -2,8 +2,11 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

29
.readthedocs.yaml Normal file
View File

@ -0,0 +1,29 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

View File

@ -1,11 +0,0 @@
language: java
sudo: false
# jdk: openjdk8
# dist: xenial
before_install:
- chmod +x ./gradlew
script:
- ./gradlew test

32
CompilerDevelopment.md Normal file
View File

@ -0,0 +1,32 @@
#### Just a few remarks upfront:
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
#### Problems with `compilerAst`:
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order:
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
- from the local file system (use case: user programs)
- from resources (prog8lib)
- from plain strings (for testing)
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
- plus, optionally, method's for registering/unregistering a listener with the parser
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
6. move `IStringEncoding` to module `compiler`
7. same with `ModuleImporter`, then rewrite that (addressing #46)
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
9. re-think `IStringEncoding` to address #38

View File

@ -1,3 +1,10 @@
This sofware license is for Prog8 the compiler + associated libraries.
The software generated by running the compiler is excluded from this.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@ -1,5 +1,3 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
@ -7,9 +5,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
*Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE*
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
@ -19,27 +14,36 @@ Documentation
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
Software license
----------------
GNU GPL 3.0, see file LICENSE
- prog8 (the compiler + libraries) is licensed under GNU GPL 3.0
- *exception:* the resulting files created by running the compiler are free to use in whatever way desired.
What does Prog8 provide?
------------------------
- big reduction of source code length over raw assembly
- reduction of source code length over raw assembly
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with an input- and output parameter signature
- no stack frame allocations because parameters and local variables are automatically allocated statically
- constant folding in expressions and other high-level program optimizations
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- floating point operations (requires the C64 Basic ROM routines for this)
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:*

10
build.gradle Normal file
View File

@ -0,0 +1,10 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}

View File

@ -0,0 +1,46 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,16 @@
<?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="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
</component>
</module>

View File

@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake, and to not spread the test cases around too much.

View File

@ -0,0 +1,3 @@
package prog8.compiler.target
class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,42 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition()
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -0,0 +1,46 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition()
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -0,0 +1,78 @@
package prog8.compiler.target.c64
import prog8.ast.base.DataType
import prog8.compiler.target.cbm.Mflpt5
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
class C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
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 POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
}

View File

@ -0,0 +1,73 @@
package prog8.compiler.target.c64
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, 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 $fb+$fc
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04u..0xf9u)
free.add(0xffu)
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernal work storage area'
).map{it.toUInt()})
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zeropage locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
// 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(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
free.clear()
}
}
removeReservedFromFreePool()
}
}

View File

@ -1,24 +1,41 @@
package prog8.compiler.target.c64
package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list"
class AssemblyProgram(
override val valid: Boolean,
override val name: String,
outputDir: Path,
private val compTarget: String) : IAssemblyProgram {
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
override fun assemble(options: CompilationOptions) {
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
if(quiet)
command.add("--quiet")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
@ -35,13 +52,11 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
if (result == 0) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
return result
}
private fun removeGeneratedLabelsFromMonlist() {
@ -71,3 +86,18 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText())
else
Ok(SourceCode.File(Path(filename)).readText())
}
}

View File

@ -0,0 +1,78 @@
package prog8.compiler.target.cbm
import prog8.compilerinterface.IMachineFloat
import prog8.compilerinterface.InternalCompilerException
import kotlin.math.absoluteValue
import kotlin.math.pow
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
companion object {
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val FLOAT_MEM_SIZE = 5
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
val sign = if (flt < 0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while (mantissa >= 0x100000000) {
mantissa /= 2.0
exponent++
}
// if mantissa is too small, shift left and adjust exponent
while (mantissa < 0x80000000) {
mantissa *= 2.0
exponent--
}
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toUByte(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
(mantLong.and(0x000000ffL)).toUByte()
)
}
}
}
}
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0.toInt() - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result
}
override fun makeFloatFillAsm(): String {
val b0 = "$" + b0.toString(16).padStart(2, '0')
val b1 = "$" + b1.toString(16).padStart(2, '0')
val b2 = "$" + b2.toString(16).padStart(2, '0')
val b3 = "$" + b3.toString(16).padStart(2, '0')
val b4 = "$" + b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,446 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMachineDefinition
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
var numberOfOptimizations = 0
var linesByFour = getLinesBy(lines, 4)
var mods = optimizeUselessStackByteWrites(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods = optimizeIncDec(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods = optimizeCmpSequence(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods = optimizeStoreLoadSame(linesByFour, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods= optimizeJsrRts(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
// TODO more assembly optimizations
return numberOfOptimizations
}
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
if(modification.remove)
lines.removeAt(modification.lineIndex)
else
lines[modification.lineIndex] = modification.replacement!!
}
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
// beq check_prog8_s72choice_32
// lda $ce01,x
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
}
}
return mods
}
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies.
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return mods
}
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
val mods = mutableListOf<Modification>()
for (lines in linesByFourteen) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
val fifth = lines[4].value.trimStart()
val sixth = lines[5].value.trimStart()
val seventh = lines[6].value.trimStart()
val eighth = lines[7].value.trimStart()
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = fifth.substring(4)
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
val address1 = getAddressArg(first, program)
val address2 = getAddressArg(second, program)
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
}
}
}
if(first.startsWith("lda") && second.startsWith("sta") && third.startsWith("lda") && fourth.startsWith("sta")) {
val firstvalue = first.substring(4)
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address))
mods.add(Modification(lines[2].index, true, null))
}
}
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = lines[8].value.trimStart()
val tenth = lines[9].value.trimStart()
val eleventh = lines[10].value.trimStart()
val twelveth = lines[11].value.trimStart()
val thirteenth = lines[12].value.trimStart()
val fourteenth = lines[13].value.trimStart()
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
mods.add(Modification(lines[7].index, true, null))
mods.add(Modification(lines[8].index, true, null))
mods.add(Modification(lines[9].index, true, null))
mods.add(Modification(lines[10].index, true, null))
}
}
}
var overlappingMods = false
/*
sta prog8_lib.retval_intermX ; remove
sty prog8_lib.retval_intermY ; remove
lda prog8_lib.retval_intermX ; remove
ldy prog8_lib.retval_intermY ; remove
sta A1
sty A2
*/
if(first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")
&& fifth.startsWith("st") && sixth.startsWith("st")) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
val reg4 = fourth[2]
val reg5 = fifth[2]
val reg6 = sixth[2]
if (reg1 == reg3 && reg1 == reg5 && reg2 == reg4 && reg2 == reg6) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue.contains("prog8_lib.retval_interm") && secondvalue.contains("prog8_lib.retval_interm")
&& firstvalue==thirdvalue && secondvalue==fourthvalue) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
overlappingMods = true
}
}
}
/*
sta A1
sty A2
lda A1 ; can be removed
ldy A2 ; can be removed if not followed by a branch instuction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
val reg4 = fourth[2]
if(reg1==reg3 && reg2==reg4) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
if (!fifth.startsWith('b'))
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
/*
sta A1
sty A2
lda A1 ; can be removed if not followed by a branch instruction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && !fourth.startsWith("b")) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3) {
val firstvalue = first.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
}
/*
sta A1
ldy A1 ; make tay
sta A1 ; remove
*/
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
&& third.startsWith("sta") && second.length>4) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A
sta A
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
if(first[2]==second[2]) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
if(firstvalue==secondvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[1].index, true, null))
}
}
}
}
}
return mods
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("lda ") && second.startsWith("lda ")) ||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
(first.startsWith("sta ") && second.startsWith("lda ")) ||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val third = lines[3].value.trimStart()
val attemptRemove =
if(third.startsWith("b")) {
// a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used)
val loadinstruction = second.substring(0, 3)
lines[0].value.trimStart().startsWith(loadinstruction)
}
else {
// no branch instruction follows, we can remove the load instruction
val address = getAddressArg(lines[2].value, program)
address==null || !machine.isIOAddress(address)
}
if(attemptRemove) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc)
mods.add(Modification(lines[2].index, true, null))
}
}
else if(first=="pha" && second=="pla" ||
first=="phx" && second=="plx" ||
first=="phy" && second=="ply" ||
first=="php" && second=="plp") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
} else if(first=="pha" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tax"))
} else if(first=="pha" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tay"))
} else if(first=="phx" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txa"))
} else if(first=="phx" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txy"))
} else if(first=="phy" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tya"))
} else if(first=="phy" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tyx"))
}
}
return mods
}
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private fun getAddressArg(line: String, program: Program): UInt? {
val loadArg = line.trimStart().substring(3).trim()
return when {
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
loadArg.startsWith('#') -> null
loadArg.startsWith('(') -> null
loadArg[0].isLetter() -> {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
if(decl!=null) {
when(decl.type){
VarDeclType.VAR -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
}
}
else null
} else null
}
else -> loadArg.substring(1).toUIntOrNull()
}
}
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
}
return mods
}
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
}
return mods
}

View File

@ -0,0 +1,59 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.second.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
when (expr) {
is ArrayIndexedExpression -> {
return paramRegisters.any {
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCall -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
}
else -> return !expr.isSimple
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
@ -10,11 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
@ -33,7 +34,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(discardResult && resultToStack)
throw AssemblyError("cannot both discard the result AND put it onto stack")
val sscope = (fcall as Node).definingSubroutine()
val sscope = (fcall as Node).definingSubroutine
when (func.name) {
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
@ -45,7 +46,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sin8", "sin8u", "sin16", "sin16u",
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sinr8", "sinr8u", "sinr16", "sinr16u",
"cos8", "cos8u", "cos16", "cos16u",
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
@ -64,7 +67,349 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
else -> TODO("missing asmgen for builtin func ${func.name}")
"push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func)
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
"rrestore" -> funcRrestore()
"rrestorex" -> funcRrestoreX()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
}
}
private fun funcRsave() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
php
pha
phy
phx""")
else
// see http://6502.org/tutorials/register_preservation.html
asmgen.out("""
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG""")
}
private fun funcRsaveX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" phx")
else
asmgen.out(" txa | pha")
}
private fun funcRrestore() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
plx
ply
pla
plp""")
else
asmgen.out("""
pla
tay
pla
tax
pla
plp""")
}
private fun funcRrestoreX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" plx")
else
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
// note: because A is pushed first so popped last, saving A is often not required here.
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) {
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
}
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG")
asmgen.out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
asmgen.out(" lda P8ZP_SCRATCH_REG")
}
else {
if (func.name == "pop") {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> asmgen.out(" plx")
RegisterOrPair.Y -> asmgen.out(" ply")
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tay")
}
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (asmgen.isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" plx | pla")
RegisterOrPair.AY -> asmgen.out(" ply | pla")
RegisterOrPair.XY -> asmgen.out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
if (func.name == "pop") {
asmgen.out(" pla")
asmgen.assignRegister(RegisterOrPair.A, tgt)
} else {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" ply | pla")
else
asmgen.out(" pla | tay | pla")
asmgen.assignRegister(RegisterOrPair.AY, tgt)
}
}
}
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(func.name=="push") {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
asmgen.out(" pha")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callfar (jsrfar) requires constant arguments")
if(address !in 0xa000..0xbfff)
throw AssemblyError("callfar done on address outside of cx16 banked ram")
if(bank==0)
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
is NumericLiteralValue -> {
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
.word ${address.toHex()}
.byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""")
}
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCallRom(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt()
if(bank==null || address==null)
throw AssemblyError("callrom requires constant arguments")
if(address !in 0xc000..0xffff)
throw AssemblyError("callrom done on address outside of cx16 banked rom")
if(bank>=32)
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
jsr ${address.toHex()}
pla
sta $01""")
} else {
when(argAddrArg) {
is AddressOf -> {
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr ${address.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
pla
sta $01""")
}
is NumericLiteralValue -> {
asmgen.out("""
lda $01
pha
lda #${bank}
sta $01
lda ${argAddrArg.number.toHex()}
jsr ${address.toHex()}
sta ${argAddrArg.number.toHex()}
pla
sta $01""")
}
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
}
}
}
private fun funcCmp(fcall: IFunctionCall) {
val arg1 = fcall.args[0]
val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
when (arg2) {
is IdentifierReference -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
}
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number}")
}
is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteralValue) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else {
// dt1 is a word
if(dt2 in WordDatatypes) {
when (arg2) {
is IdentifierReference -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy ${asmgen.asmVariableName(arg2)}+1
bne +
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy #>${arg2.number}
bne +
cmp #<${arg2.number}
+""")
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
}
} else
throw AssemblyError("args for cmp() should have same dt")
}
}
@ -73,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("should not discard result of memory allocation at $fcall")
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
val existingSize = asmgen.slabs[name]
if(existingSize!=null && existingSize!=size)
@ -86,7 +431,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size
@ -98,7 +443,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -108,13 +453,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
else
when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> {
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "cos16", "cos16u" -> {
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
}
@ -193,7 +538,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRor2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@ -236,7 +581,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRor(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@ -251,7 +596,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -294,7 +639,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRol2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@ -337,7 +682,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcRol(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
@ -352,7 +697,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -394,8 +739,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
val indexerExpr = if(indexer.indexVar!=null) indexer.indexVar!! else indexer.indexNum!!
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
@ -404,7 +748,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr floats.func_${func.name}_stack")
else {
asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
}
@ -412,7 +756,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
@ -421,7 +765,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@ -429,7 +773,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -437,20 +781,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -458,7 +802,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
@ -467,26 +811,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@ -497,7 +841,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
@ -506,26 +850,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@ -541,11 +885,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
if(dt istype DataType.BYTE || dt istype DataType.UBYTE) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
return
}
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
if(dt istype DataType.WORD || dt istype DataType.UWORD) {
asmgen.out("""
ldy $firstName
lda $secondName
@ -558,7 +902,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
""")
return
}
if(dt.istype(DataType.FLOAT)) {
if(dt istype DataType.FLOAT) {
asmgen.out("""
lda #<$firstName
sta P8ZP_SCRATCH_W1
@ -576,7 +920,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
@ -599,6 +942,49 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
return
}
addr1==null && addr2==null && name1==null && name2==null -> {
val firstExpr = first.addressExpression as? BinaryExpression
val secondExpr = second.addressExpression as? BinaryExpression
if(firstExpr!=null && secondExpr!=null) {
val pointerVariable = firstExpr.left as? IdentifierReference
val firstOffset = firstExpr.right
val secondOffset = secondExpr.right
if(pointerVariable != null
&& pointerVariable isSameAs secondExpr.left
&& firstExpr.operator == "+" && secondExpr.operator == "+"
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) {
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset!=secondOffset) {
swapArrayValues(
DataType.UBYTE,
asmgen.asmVariableName(pointerVariable), firstOffset,
asmgen.asmVariableName(pointerVariable), secondOffset
)
return
}
} else if(firstOffset is TypecastExpression && secondOffset is TypecastExpression) {
if(firstOffset.type in WordDatatypes && secondOffset.type in WordDatatypes) {
val firstOffsetVar = firstOffset.expression as? IdentifierReference
val secondOffsetVar = secondOffset.expression as? IdentifierReference
if(firstOffsetVar!=null && secondOffsetVar!=null) {
if(firstOffsetVar!=secondOffsetVar) {
swapArrayValues(
DataType.UBYTE,
asmgen.asmVariableName(pointerVariable), firstOffsetVar,
asmgen.asmVariableName(pointerVariable), secondOffsetVar
)
return
}
}
}
} else if(firstOffset is IdentifierReference || secondOffset is IdentifierReference) {
throw AssemblyError("expected a typecast-to-word for index variable at ${firstOffset.position} and/or ${secondOffset.position}")
}
}
}
}
}
}
@ -606,14 +992,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementIDt = first.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
val secondNum = second.indexer.indexNum
val secondVar = second.indexer.indexVar
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
val firstVar = first.indexer.indexExpr as? IdentifierReference
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
val secondVar = second.indexer.indexExpr as? IdentifierReference
if(firstNum!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
@ -634,15 +1018,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
return when (expr) {
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), memory = DirectMemoryWrite(expr.addressExpression, expr.position))
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine, variableAsmName = asmgen.asmVariableName(expr))
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine, array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
else -> throw AssemblyError("invalid expression object $expr")
}
}
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
when(datatype) {
when(val datatype: DataType = first.inferType(program).getOr(DataType.UNDEFINED)) {
in ByteDatatypes, in WordDatatypes -> {
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
asmgen.assignExpressionToVariable(second, "P8ZP_SCRATCH_W2", datatype, null)
@ -660,21 +1043,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateNormalAssignment(assignSecond)
}
DataType.FLOAT -> {
// via evaluation stack
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
// via temp variable and FAC1
asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
targetFromExpr(first, datatype),
false, program.memsizer, first.position
)
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
targetFromExpr(second, datatype),
false, program.memsizer, second.position
)
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
else -> throw AssemblyError("weird swap dt")
}
@ -714,7 +1099,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -787,7 +1172,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
bcc +
inc P8ZP_SCRATCH_W2+1
+ jsr floats.swap_floats
+ jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -845,7 +1230,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ jsr floats.swap_floats
+ jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -903,7 +1288,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
jsr floats.func_swap_f
""")
}
else -> throw AssemblyError("invalid aray elt type")
@ -912,7 +1297,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
if(resultToStack) {
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
@ -924,15 +1309,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt) {
in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), RegisterOrPair.FAC1)
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type")
}
@ -946,7 +1331,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
"rndw" -> {
@ -954,7 +1339,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
@ -973,7 +1358,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
@ -995,20 +1380,24 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
}
else -> throw AssemblyError("wrong pokew arg type")
}
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
@ -1050,15 +1439,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
@ -1077,7 +1472,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
RegisterOrPair.AY -> {}
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
RegisterOrPair.XY -> asmgen.out(" tax")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().toLowerCase()} | sty cx16.${resultRegister.toString().toLowerCase()}+1")
in Cx16VirtualRegisters -> asmgen.out(
" sta cx16.${
resultRegister.toString().lowercase()
} | sty cx16.${resultRegister.toString().lowercase()}+1")
else -> throw AssemblyError("invalid reg")
}
}
@ -1127,9 +1525,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}")
asmgen.out(" sta cx16.${reg.toString().lowercase()}")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}+1")
asmgen.out(" sta cx16.${reg.toString().lowercase()}+1")
}
else -> throw AssemblyError("invalid mkword target reg")
}
@ -1138,7 +1536,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
if (!arg.inferType(program).isWords)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("msb(const) should have been const-folded away")
@ -1182,7 +1580,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
if (!arg.inferType(program).isWords)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("lsb(const) should have been const-folded away")
@ -1246,7 +1644,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.STRUCT) })
val callConv = signature.callConvention(args.map {
it.inferType(program).getOrElse { throw AssemblyError("unknown dt")}
})
fun getSourceForFloat(value: Expression): AsmAssignSource {
return when (value) {
@ -1305,7 +1705,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignSource.fromAstSource(value, program, asmgen)
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}

View File

@ -0,0 +1,790 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
}
translateExpressionInternal(expression)
}
// the rest of the methods are all PRIVATE
private fun translateExpressionInternal(expression: Expression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translateExpression(expression)
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
}
}
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
// only for use in nested expression evaluation
val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else {
sub as Subroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
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 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
rol a
sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
}
}
}
private fun translateExpression(typecast: TypecastExpression) {
translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.FLOAT -> {
when(typecast.type) {
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
DataType.FLOAT -> {}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmVariableName(expr.identifier)
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: NumericLiteralValue) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
lda #<${expr.number.toHex()}
sta P8ESTACK_LO,x
lda #>${expr.number.toHex()}
sta P8ESTACK_HI,x
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatAsmConst(expr.number)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmVariableName(expr)
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
}
in IterableDatatypes -> {
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
}
else -> throw AssemblyError("stack push weird variable type $expr")
}
}
private fun translateExpression(expr: BinaryExpression) {
// Uses evalstack to evaluate the given expression.
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
// see if we can apply some optimized routines
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) {
translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(leftVal<0) {
repeat(leftVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(leftVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
else if (rightVal!=null && rightVal in -4..4)
{
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal<0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
"-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4)
{
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal>0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpressionInternal(expr.left)
when (leftDt) {
DataType.UBYTE -> {
if (amount <= 2)
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x")
repeat(amount) { asmgen.out(" lsr a") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
}
DataType.BYTE -> {
if (amount <= 2)
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
}
DataType.UWORD -> {
if(amount>=16) {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
return
}
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_right_uw_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_uw_$left")
}
DataType.WORD -> {
if(amount>=16) {
asmgen.out("""
lda P8ESTACK_HI+1,x
bmi +
lda #0
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
beq ++
+ lda #255
sta P8ESTACK_LO+1,x
sta P8ESTACK_HI+1,x
+""")
return
}
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_right_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_w_$left")
}
else -> throw AssemblyError("weird type")
}
return
}
}
"<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) {
if (amount <= 2)
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
else {
asmgen.out(" lda P8ESTACK_LO+1,x")
repeat(amount) { asmgen.out(" asl a") }
asmgen.out(" sta P8ESTACK_LO+1,x")
}
} else {
var left = amount
while (left >= 7) {
asmgen.out(" jsr math.shift_left_w_7")
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
else
asmgen.out(" jsr math.shift_left_w_$left")
}
return
}
}
"*" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVar = expr.left as? IdentifierReference
val rightVar = expr.right as? IdentifierReference
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
return translateSquared(leftVar, leftDt)
}
val value = expr.right.constValue(program)
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount==2) {
// optimize x*2 common case
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x")
} else {
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
}
return
}
when(rightDt) {
DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
}
DataType.BYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
return
}
}
DataType.UWORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
}
DataType.WORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
return
}
}
else -> {}
}
}
}
}
"/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpressionInternal(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt")
}
return
}
}
}
}
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpressionInternal(expr.left)
translateExpressionInternal(expr.right)
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
}
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable)
when(dt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" lda $asmVar")
asmgen.signExtendAYlsb(dt)
asmgen.out(" jsr math.square")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
}
else -> throw AssemblyError("require integer dt for square")
}
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: PrefixExpression) {
translateExpressionInternal(expr.expression)
val itype = expr.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when(expr.operator) {
"+" -> {}
"-" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
else -> throw AssemblyError("weird type")
}
}
"~" -> {
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda P8ESTACK_LO+1,x
eor #255
sta P8ESTACK_LO+1,x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
}
}
"not" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
}
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
val constIndexNum = arrayExpr.indexer.constIndex()
if(constIndexNum!=null) {
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
}
else -> throw AssemblyError("weird element type")
}
} else {
when(elementDt) {
in ByteDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
}
in WordDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
asmgen.out("""
ldy #>$arrayVarName
clc
adc #<$arrayVarName
bcc +
iny
+ jsr floats.push_float""")
}
else -> throw AssemblyError("weird dt")
}
}
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
if(types==DataType.BYTE)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda P8ESTACK_LO+2,x
clc
adc P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
""")
"-" -> asmgen.out("""
lda P8ESTACK_LO+2,x
sec
sbc P8ESTACK_LO+1,x
inx
sta P8ESTACK_LO+1,x
""")
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
"and" -> asmgen.out(" jsr prog8_lib.and_b")
"or" -> asmgen.out(" jsr prog8_lib.or_b")
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
"%" -> {
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_uw")
}
"+" -> asmgen.out(" jsr prog8_lib.add_w")
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
"<<" -> asmgen.out(" jsr math.shift_left_w")
">>" -> {
if(dt==DataType.UWORD)
asmgen.out(" jsr math.shift_right_uw")
else
asmgen.out(" jsr math.shift_right_w")
}
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w")
"or" -> asmgen.out(" jsr prog8_lib.or_w")
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorFloats(operator: String) {
when(operator) {
"**" -> asmgen.out(" jsr floats.pow_f")
"*" -> asmgen.out(" jsr floats.mul_f")
"/" -> asmgen.out(" jsr floats.div_f")
"+" -> asmgen.out(" jsr floats.add_f")
"-" -> asmgen.out(" jsr floats.sub_f")
"<" -> asmgen.out(" jsr floats.less_f")
">" -> asmgen.out(" jsr floats.greater_f")
"<=" -> asmgen.out(" jsr floats.lesseq_f")
">=" -> asmgen.out(" jsr floats.greatereq_f")
"==" -> asmgen.out(" jsr floats.equal_f")
"!=" -> asmgen.out(" jsr floats.notequal_f")
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
when(operator) {
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
"<=" -> asmgen.out("""
bpl +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
">=" -> asmgen.out("""
bmi +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO,x""")
"<" -> asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
">" -> asmgen.out("""
bpl +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x""")
}
asmgen.out(" dex")
}
}

View File

@ -1,14 +1,15 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.ArrayToElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.toConstantIntegerRange
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -21,13 +22,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr)
} else {
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
@ -40,6 +41,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number
if(limit==0.0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
@ -49,17 +57,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel""")
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
@ -67,8 +75,8 @@ $endLabel""")
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if(stepsize>0) {
@ -117,16 +125,15 @@ $modifiedLabel2 cmp #0 ; modified
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
""")
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
@ -282,7 +289,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@ -321,7 +328,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@ -386,23 +393,25 @@ $loopLabel""")
}
-2 -> {
when (range.last) {
0 -> asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname""")
asmgen.jmp(loopLabel)
}
1 -> asmgen.out("""
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
else -> asmgen.out("""
dec $varname
dec $varname
lda $varname
cmp #${range.last-2}
bne $loopLabel""")
dec $varname
dec $varname
lda $varname
cmp #${range.last-2}
bne $loopLabel""")
}
}
else -> {
@ -413,8 +422,8 @@ $loopLabel""")
beq $endLabel
clc
adc #${range.step}
sta $varname
jmp $loopLabel""")
sta $varname""")
asmgen.jmp(loopLabel)
}
}
asmgen.out(endLabel)
@ -450,9 +459,9 @@ $loopLabel""")
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
jmp $loopLabel
$endLabel""")
sta $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
@ -502,9 +511,9 @@ $loopLabel""")
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
jmp $loopLabel
$endLabel""")
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
1 -> {
asmgen.out("""
@ -545,9 +554,9 @@ $loopLabel""")
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
$endLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
@ -573,12 +582,16 @@ $loopLabel""")
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel
$endLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.loopVar),
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
stmt.definingSubroutine)
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
@ -6,19 +6,19 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
translateFunctionCall(stmt, false)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
@ -27,11 +27,21 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!)
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
}
}
internal fun saveXbeforeCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
}
}
@ -39,105 +49,105 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(stmt: IFunctionCall) {
// Output only the code to setup the parameters and perform the actual call
internal fun restoreXafterCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
asmgen.out(" jsr $subAsmName")
}
}
if(sub.inline && asmgen.options.optimize) {
if(sub.containsDefinedVariables())
throw AssemblyError("can't inline sub with vars")
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
throw AssemblyError("can't inline a non-asm subroutine with parameters")
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach { asmgen.translate(it) }
}
else {
asmgen.out(" jsr $subName")
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
argumentsViaVariables(sub, call)
asmgen.out(" jsr $subAsmName")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first, arg.second)
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
}
}
}
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
@ -152,11 +162,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
@ -168,21 +178,31 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
when (sub.parameters[argi.index].type) {
in ByteDatatypes -> {
// only load the lsb of the virtual register
asmgen.out("""
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
asmgen.out(
" lda #0 | sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
}
in WordDatatypes ->
asmgen.out("""
in WordDatatypes, in IterableDatatypes ->
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1
""")
else -> throw AssemblyError("weird dt")
}
@ -194,11 +214,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
clc
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
@ -235,22 +254,18 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
@ -277,11 +292,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
clc
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
@ -302,16 +316,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
else {
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
}
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -15,11 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine()
val scope = stmt.definingSubroutine
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@ -65,9 +65,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
val constIndex = targetArrayIdx.indexer.constIndex()
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {

View File

@ -1,12 +1,12 @@
package prog8.compiler.target.c64.codegen.assignment
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
internal enum class TargetStorageKind {
@ -39,13 +39,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val origAstTarget: AssignTarget? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
}
lateinit var origAssign: AsmAssignment
@ -55,27 +56,40 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
with(assign.target) {
val idt = inferType(program)
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
when {
identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
if (parameter!=null) {
val sub = parameter.definingSubroutine!!
if (sub.isAsmSubroutine) {
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null)
throw AssemblyError("can't assign value to processor statusflag directly")
else
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
}
}
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
}
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
}
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
RegisterOrPair.R0,
@ -93,7 +107,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
}
}
}
@ -110,8 +124,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val expression: Expression? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String
get() = if(array==null)
@ -120,13 +134,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
return when {
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
else -> throw AssemblyError("weird indexer")
}
}
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource = fromAstSource(indexer.indexExpr, program, asmgen)
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
@ -134,16 +142,19 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) {
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
is NumericLiteralValue -> throw AssemblyError("should have been constant value")
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
val regStr = varName.toLowerCase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
val regStr = varName.lowercase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.uppercase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
@ -153,7 +164,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
@ -166,9 +177,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
is BuiltinFunctionStatementPlaceholder -> {
val returnType = value.inferType(program)
if(!returnType.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
else -> {
throw AssemblyError("weird call")
@ -177,9 +186,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
else -> {
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
}
}
@ -211,10 +218,10 @@ internal class AsmAssignment(val source: AsmAssignSource,
val position: Position) {
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size"
"source dt size must be less or equal to target dt size at $position"
}
}
}

View File

@ -1,22 +1,20 @@
package prog8.compiler.target.c64.codegen.assignment
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
private val exprAsmgen: ExpressionsAsmGen) {
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
@ -31,15 +29,27 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
} else origtarget
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
return
}
when(assign.source.kind) {
SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number
val num = assign.source.number!!.number
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toInt())
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
DataType.FLOAT -> assignConstantFloat(assign.target, num)
else -> throw AssemblyError("weird numval type")
}
}
@ -64,9 +74,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val value = assign.source.array!!
val elementDt = assign.source.datatype
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) {
val constIndex = value.indexer.constIndex()
if (constIndex!=null) {
// constant array index value
val indexValue = value.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when (elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue")
@ -113,18 +124,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(assign.target, CpuRegister.A)
}
val value = assign.source.memory!!
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
@ -143,7 +151,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program) ?: asmgen.asmVariableName(value.identifier)
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
@ -155,7 +163,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
@ -169,13 +177,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
@ -215,7 +223,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.typeOrElse(DataType.STRUCT)) {
when(returntype.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
@ -247,30 +255,41 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions to do it via registers here,
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
if (value.parent is Return) {
if (this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
}
exprAsmgen.translateExpression(value)
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
SourceStorageKind.REGISTER -> {
when(assign.source.datatype) {
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
asmgen.assignRegister(assign.source.register!!, assign.target)
}
SourceStorageKind.STACK -> {
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
@ -295,9 +314,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origTypeCastExpression: TypecastExpression) {
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(valueDt==targetDt)
throw AssemblyError("type cast to identical dt should have been removed")
@ -318,17 +335,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if(targetDt in WordDatatypes) {
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A)
}
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
@ -356,22 +370,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// special case optimizations
if(target.kind==TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.STRUCT)
if(target.kind== TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
when (valueDt) {
in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A)
assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
}
in WordDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
}
DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignTypecastedFloatFAC1(target.asmVarname, targetDt)
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
}
in PassByReferenceDatatypes -> {
// str/array value cast (most likely to UWORD, take address-of)
@ -396,15 +410,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
// 'cast' a ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register)
// 'cast' an ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register, false)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
@ -422,19 +436,41 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
}
// give up, do it via eval stack
// TODO optimize typecasts for more special cases?
// note: cannot use assignTypeCastedValue because that is ourselves :P
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
assignStackValue(target)
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) {
DataType.UBYTE -> {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.out(" jsr floats.FREADUY")
}
DataType.BYTE -> {
assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.out(" jsr floats.FREADSA")
}
DataType.UWORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.out(" jsr floats.GIVUAYFAY")
}
DataType.WORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" jsr floats.GIVAYFAY")
}
else -> throw AssemblyError("invalid dt")
}
if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF")
}
} else {
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
}
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@ -445,7 +481,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
if(targetDt==DataType.FLOAT)
throw AssemblyError("typecast to identical type")
@ -569,11 +605,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
TODO("assign typecasted string into target var")
}
DataType.STR -> throw AssemblyError("cannot typecast a string value")
else -> throw AssemblyError("weird type")
}
}
@ -589,13 +621,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.UBYTE -> {
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.FLOAT -> {
when(regs) {
@ -617,13 +655,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.BYTE -> {
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
DataType.UWORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.WORD -> {
when(regs) {
@ -655,7 +699,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.UWORD -> {
when(targetDt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
DataType.WORD, DataType.UWORD -> {
when(regs) {
@ -683,7 +727,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.WORD -> {
when(targetDt) {
DataType.BYTE, DataType.UBYTE -> {
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
DataType.WORD, DataType.UWORD -> {
when(regs) {
@ -708,11 +752,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
TODO("assign typecasted string into target var")
}
DataType.STR -> throw AssemblyError("cannot typecast a string value")
else -> throw AssemblyError("weird type")
}
}
@ -762,7 +802,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@ -828,12 +868,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" inx | txy | ldx #0 | lda P8ESTACK_LO,y")
RegisterOrPair.AY -> asmgen.out(" inx | ldy #0 | lda P8ESTACK_LO,x")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
@ -845,12 +886,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY-> asmgen.out(" inx | ldy P8ESTACK_HI,x | lda P8ESTACK_LO,x")
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda P8ESTACK_HI,x
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't assign word to single byte register")
@ -894,11 +936,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda #<$sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #>$sourceName
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't load address in a single 8-bit register")
@ -945,7 +988,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
lda #<$sourceName
ldy #>$sourceName+1
sta P8ESTACK_LO,x
sty P8ESTACK_HI,x
tya
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("string-assign to weird target")
@ -968,7 +1012,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@ -1023,7 +1067,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
@ -1035,11 +1079,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda $sourceName+1
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
@ -1056,6 +1101,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFAC2float(target: AsmAssignTarget) {
asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1
assignFAC1float(target)
}
internal fun assignFAC1float(target: AsmAssignTarget) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1073,11 +1123,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float_from_fac1")
@ -1109,11 +1159,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
@ -1156,11 +1206,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
@ -1191,7 +1241,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
@ -1209,11 +1259,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@ -1242,11 +1293,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
assignStackValue(wordtarget)
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
}
else {
asmgen.saveRegisterLocal(CpuRegister.X, wordtarget.scope!!)
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.X)
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
TargetStorageKind.REGISTER -> {
when(wordtarget.register!!) {
@ -1302,7 +1362,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
@ -1339,12 +1399,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
if(target.register !in Cx16VirtualRegisters)
require(target.datatype in ByteDatatypes)
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
} else {
require(target.datatype in ByteDatatypes)
}
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
when(register) {
@ -1368,7 +1438,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
val indexVar = target.array!!.indexer.indexExpr as IdentifierReference
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -1383,7 +1454,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sta cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" sta cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@ -1397,7 +1468,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" stx cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" stx cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@ -1411,7 +1482,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sty cx16.${target.register.toString().toLowerCase()}")
asmgen.out(" sty cx16.${target.register.toString().lowercase()}")
}
else -> throw AssemblyError("weird register")
}
@ -1428,7 +1499,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes)
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
@ -1451,7 +1522,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val idx = target.constArrayIndexValue!! * 2
val idx = target.constArrayIndexValue!! * 2u
when (regs) {
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
@ -1501,9 +1572,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { }
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
stx cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
stx cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@ -1513,9 +1585,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { asmgen.out(" tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
sty cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@ -1525,9 +1598,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { }
in Cx16VirtualRegisters -> {
asmgen.out("""
stx cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
asmgen.out(
"""
stx cx16.${target.register.toString().lowercase()}
sty cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
@ -1552,7 +1626,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
@ -1578,7 +1652,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" stz ${target.asmVarname} | stz ${target.asmVarname}+1")
}
TargetStorageKind.MEMORY -> {
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
throw AssemblyError("memory is bytes not words")
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
@ -1594,7 +1668,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
target.register.toString().lowercase()
} | stz cx16.${target.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("invalid register for word value")
}
@ -1644,11 +1721,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda #<${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #>${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("invalid register for word value")
@ -1665,15 +1743,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" stz ${target.asmVarname} ")
}
TargetStorageKind.MEMORY -> {
asmgen.out(" lda #${byte.toHex()}")
asmgen.out(" lda #0")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
@ -1695,7 +1773,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
asmgen.out(
" stz cx16.${
target.register.toString().lowercase()
} | stz cx16.${target.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("weird register")
}
@ -1735,11 +1816,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
asmgen.out(
" lda #${byte.toHex()} | sta cx16.${
target.register.toString().lowercase()
}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
asmgen.out(" stz cx16.${target.register.toString().lowercase()}+1\n")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
asmgen.out(
" lda #0 | sta cx16.${
target.register.toString().lowercase()
}+1\n")
}
else -> throw AssemblyError("weird register")
}
@ -1776,8 +1863,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
val constIndex = target.array!!.indexer.constIndex()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}+$indexValue
@ -1796,7 +1884,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta ${target.asmVarname}+$indexValue+4
""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
@ -1841,8 +1929,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
val constIndex = target.array!!.indexer.constIndex()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
@ -1856,7 +1945,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $arrayVarName+$indexValue+4
""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out("""
lda #<${constFloat}
sta P8ZP_SCRATCH_W1
@ -1888,7 +1977,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByte(target: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1913,11 +2002,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
asmgen.out(
"""
lda ${address.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@ -1953,10 +2043,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
asmgen.out(
"""
sta cx16.${target.register.toString().lowercase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("weird register")
@ -1970,7 +2061,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
@ -2040,53 +2131,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeViaExprEval() {
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.out(" pla")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
}
}
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
sta (P8ZP_SCRATCH_W2)""")
}
} else {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" ldy #0 | sta ($sourceName),y")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
@ -2096,7 +2149,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" sta ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
storeAIntoPointerVar(addressExpr)
asmgen.storeAIntoPointerVar(addressExpr)
}
addressExpr is BinaryExpression -> {
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
@ -2106,9 +2159,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
@ -2120,8 +2173,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)

View File

@ -0,0 +1,102 @@
package prog8.compiler.target.cx16
import prog8.ast.base.DataType
import prog8.compiler.target.cbm.Mflpt5
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
class CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
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 POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "x16emu"
extraArgs = emptyList()
}
2 -> {
emulatorName = "box16"
extraArgs = listOf("-sym", "${programNameWithPath}.$viceMonListPostfix")
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
return
}
}
for(emulator in listOf(emulatorName)) {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> {
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
for(reg in 0..15) {
vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15
vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s
vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L
vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H
vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL
vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH
}
return vars
}
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "inx", "iny", "jmp", "jsr",
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
"sec", "sed", "sei",
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
"rmb", "smb", "stp", "wai")
}

View File

@ -0,0 +1,41 @@
package prog8.compiler.target.cx16
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE))
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
removeReservedFromFreePool()
}
}

View File

@ -0,0 +1,44 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,15 @@
<?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="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="compilerAst" />
</component>
</module>

View File

@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake, and to not spread the test cases around too much.

View File

@ -0,0 +1,128 @@
package prog8.optimizer
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isIOAddress
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.right)
return noModifications
if(assignment.target isSameAs binExpr.left) {
if(binExpr.right.isSimple)
return noModifications
val leftBx = binExpr.left as? BinaryExpression
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
return noModifications
val rightBx = binExpr.right as? BinaryExpression
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
return noModifications
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
// val dt = assignment.target.inferType(program)
// if(!dt.isInteger)
// return noModifications
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
// val assignTempVar = Assignment(
// AssignTarget(tempVar, null, null, binExpr.right.position),
// binExpr.right, binExpr.right.position
// )
// return listOf(
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
// )
}
if(binExpr.right.isSimple) {
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
)
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also work on
// the expressiontrees that are not used in an assignment statement...
}
val typecast = assignment.value as? TypecastExpression
if(typecast!=null) {
val origExpr = typecast.expression as? BinaryExpression
if(origExpr!=null) {
// it's a typecast of a binary expression.
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt $tempDt")
}
val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
typecast.expression, typecast.position
)
return listOf(
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast)
)
}
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget) =
if (target.identifier!=null || target.memoryAddress!=null)
!target.isIOAddress(compTarget.machine)
else
false
}

View File

@ -9,28 +9,32 @@ import kotlin.math.pow
class ConstExprEvaluator {
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
return when(operator) {
"+" -> plus(left, right)
"-" -> minus(left, right)
"*" -> multiply(left, right)
"/" -> divide(left, right)
"%" -> remainder(left, right)
"**" -> power(left, right)
"&" -> bitwiseand(left, right)
"|" -> bitwiseor(left, right)
"^" -> bitwisexor(left, right)
"and" -> logicaland(left, right)
"or" -> logicalor(left, right)
"xor" -> logicalxor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
try {
return when(operator) {
"+" -> plus(left, right)
"-" -> minus(left, right)
"*" -> multiply(left, right)
"/" -> divide(left, right)
"%" -> remainder(left, right)
"**" -> power(left, right)
"&" -> bitwiseand(left, right)
"|" -> bitwiseor(left, right)
"^" -> bitwisexor(left, right)
"and" -> logicaland(left, right)
"or" -> logicalor(left, right)
"xor" -> logicalxor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
}
} catch (ax: FatalAstException) {
throw ExpressionError(ax.message, left.position)
}
}
@ -42,14 +46,14 @@ class ConstExprEvaluator {
left.number.toInt().ushr(amount.number.toInt())
else
left.number.toInt().shr(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
@ -57,12 +61,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -74,12 +78,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -91,12 +95,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -106,11 +110,11 @@ class ConstExprEvaluator {
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
@ -119,11 +123,11 @@ class ConstExprEvaluator {
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
@ -132,11 +136,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@ -147,12 +151,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -164,12 +168,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -181,12 +185,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -198,12 +202,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -223,19 +227,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalInteger(result, left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
if(right.number==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
@ -252,19 +256,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}

View File

@ -9,12 +9,10 @@ import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
@ -42,7 +40,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
@ -50,29 +48,29 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
"~" -> when (subexpr.type) {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
@ -81,7 +79,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
return noModifications
}
/**
/*
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
@ -103,34 +101,61 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val rightconst = expr.right.constValue(program)
val modifications = mutableListOf<IAstModification>()
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
if(leftExpr!=null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
"+" -> {
// X + С1 == C2 --> X == C2 - C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
"-" -> {
// X - С1 == C2 --> X == C2 + C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
}
}
}
}
if(expr.operator == "**" && leftconst!=null) {
// optimize various simple cases of ** :
// optimize away 1 ** x into just 1 and 0 ** x into just 0
// optimize 2 ** x into (1<<x) if both operands are integer.
val leftDt = leftconst.inferType(program).typeOrElse(DataType.STRUCT)
when (leftconst.number.toDouble()) {
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
when (leftconst.number) {
0.0 -> {
val value = NumericLiteralValue(leftDt, 0, expr.position)
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
1.0 -> {
val value = NumericLiteralValue(leftDt, 1, expr.position)
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
2.0 -> {
if(rightconst!=null) {
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
} else {
val rightDt = expr.right.inferType(program).typeOrElse(DataType.STRUCT)
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val targetDt =
when (parent) {
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.STRUCT)
is Assignment -> parent.target.inferType(program).getOr(DataType.UNDEFINED)
is VarDecl -> parent.datatype
else -> leftDt
}
val one = NumericLiteralValue(targetDt, 1, expr.position)
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
modifications += IAstModification.ReplaceNode(expr, shift, parent)
}
@ -139,7 +164,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
}
}
if(expr.inferType(program).istype(DataType.FLOAT)) {
if(expr.inferType(program) istype DataType.FLOAT) {
val subExpr: BinaryExpression? = when {
leftconst != null -> expr.right as? BinaryExpression
rightconst != null -> expr.left as? BinaryExpression
@ -161,13 +186,94 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
}
}
val evaluator = ConstExprEvaluator()
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
modifications += IAstModification.ReplaceNode(expr, result, parent)
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && rightconst!=null) {
if(expr.operator=="+" || expr.operator=="-") {
if(leftBinExpr.operator in listOf("+", "-")) {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null) {
// (X + C2) +/- rightConst --> X + (C2 +/- rightConst)
// (X - C2) +/- rightConst --> X - (C2 +/- rightConst) mind the flipped right operator
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))
}
}
}
else if(expr.operator=="*") {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null) {
if(leftBinExpr.operator=="*") {
// (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))
} else if (leftBinExpr.operator=="/") {
// (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))
}
}
}
else if(expr.operator=="/") {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null && leftBinExpr.operator=="/") {
// (X / C1) / C2 --> X / (C1*C2)
// NOTE: do not optimize (X * C1) / C2 --> 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))
}
}
}
if(expr.operator=="+" || expr.operator=="-") {
if(leftBinExpr!=null && rightBinExpr!=null) {
val c1 = leftBinExpr.right.constValue(program)
val c2 = rightBinExpr.right.constValue(program)
if(leftBinExpr.operator=="+" && rightBinExpr.operator=="+") {
if (c1 != null && c2 != null) {
// (X + C1) <plusmin> (Y + C2) => (X <plusmin> Y) + (C1 <plusmin> C2)
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)
}
}
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
if (c1 != null && c2 != null) {
// (X - C1) <plusmin> (Y - C2) => (X <plusmin> Y) - (C1 <plusmin> C2)
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)
}
}
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
if (c1 != null && c2 != null && c1==c2) {
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
}
}
return modifications
}
@ -188,7 +294,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
val newArray = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
}
@ -224,7 +330,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
range.step
}
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@ -279,7 +385,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) {
if(valueDt isnot decl.datatype) {
val cast = numval.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
@ -314,7 +420,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
{
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
// todo: this implements only a small set of possible reorderings at this time
// todo: this implements only a small set of possible reorderings at this time, we could think of more
if(expr.operator==subExpr.operator) {
// both operators are the same.

View File

@ -4,28 +4,36 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// (also check range literal operands types before they get expanded into arrays for instance)
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
&& declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
@ -33,6 +41,35 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
return noModifications
}
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
val from = range.from.constValue(program)?.number
val to = range.to.constValue(program)?.number
val step = range.step.constValue(program)?.number
if(from==null) {
if(!range.from.inferType(program).isInteger)
errors.err("range expression from value must be integer", range.from.position)
} else if(from-from.toInt()>0) {
errors.err("range expression from value must be integer", range.from.position)
}
if(to==null) {
if(!range.to.inferType(program).isInteger)
errors.err("range expression to value must be integer", range.to.position)
} else if(to-to.toInt()>0) {
errors.err("range expression to value must be integer", range.to.position)
}
if(step==null) {
if(!range.step.inferType(program).isInteger)
errors.err("range expression step value must be integer", range.step.position)
} else if(step-step.toInt()>0) {
errors.err("range expression step value must be integer", range.step.position)
}
return noModifications
}
}
@ -40,7 +77,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
@ -74,8 +110,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
@ -93,19 +128,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
decl
))
}
} else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
try {
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if (cval != null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
}
}
}
@ -114,33 +136,33 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteralValue
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
@ -167,20 +189,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.constIndex() ?: return noModifications
val litval = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
@ -189,22 +209,24 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
val numericLv = decl.value as? NumericLiteralValue
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}

View File

@ -15,17 +15,16 @@ import kotlin.math.log2
import kotlin.math.pow
/*
todo add more expression optimizations
todo add more peephole expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>()
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@ -46,7 +45,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program).istype(typecast.type)) {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
@ -135,8 +134,8 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
))
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
@ -150,7 +149,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@ -160,7 +159,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@ -180,14 +179,26 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
}
if(expr.operator == ">=" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == ">=" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
// for integers: x < 1 --> x <= 0
expr.operator = "<="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == "<" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
@ -221,52 +232,62 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
constTrue
else if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
else -> null
}
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
constFalse
else if (leftVal != null && leftVal.asBooleanValue)
expr.right
else if (rightVal != null && rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
leftVal != null && leftVal.asBooleanValue -> expr.right
rightVal != null && rightVal.asBooleanValue -> expr.left
else -> null
}
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else if (leftVal != null && leftVal.asBooleanValue)
PrefixExpression("not", expr.right, expr.right.position)
else if (rightVal != null && rightVal.asBooleanValue)
PrefixExpression("not", expr.left, expr.left.position)
else
null
when {
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
else -> null
}
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
"|" -> {
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
else -> null
}
}
"^" -> {
when {
leftVal?.number==0.0 -> expr.right
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)
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
else -> null
}
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue)
constFalse
else if (rightVal != null && !rightVal.asBooleanValue)
constFalse
else
null
when {
leftVal?.number==0.0 -> constFalse
rightVal?.number==0.0 -> constFalse
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
else -> null
}
}
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
"/" -> optimizeDivision(expr, leftVal, rightVal)
@ -290,13 +311,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless lsb() of byte value that was casted to word
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless lsb() of byte value that was typecasted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
}
@ -306,20 +327,20 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless msb() of byte value that was casted to word, replace with 0
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
parent))
}
}
@ -352,7 +373,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2
when (rightConst.number.toDouble()) {
when (rightConst.number) {
0.0 -> {
// left
return expr2.left
@ -361,7 +382,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
val rnum = rightVal?.number
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
@ -382,7 +403,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rnum = rightVal.number.toDouble()
val rnum = rightVal.number
if (rnum == 0.0) {
// left
return expr.left
@ -396,7 +417,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// -right
return PrefixExpression("-", expr.right, expr.position)
@ -415,7 +436,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
when (rightConst.number) {
-3.0 -> {
// -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
@ -435,7 +456,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
0.0 -> {
// 1
return NumericLiteralValue(rightConst.type, 1, expr.position)
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
}
0.5 -> {
// sqrt(left)
@ -457,18 +478,18 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
-1.0 -> {
// -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
1.0 -> {
//1
return NumericLiteralValue(leftVal.type, 1, expr.position)
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
}
}
@ -490,10 +511,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
return null
}
}
@ -510,11 +531,11 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val cv = rightConst.number
val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
when (cv) {
-1.0 -> {
// '/' -> -left
@ -545,22 +566,22 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number.toDouble()) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
} else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number.toDouble()) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
}
}
@ -577,28 +598,28 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2
when (val cv = rightConst.number.toDouble()) {
when (val cv = rightConst.number) {
-1.0 -> {
// -left
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(rightConst.type, 0, expr.position)
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
}
1.0 -> {
// left
return expr2.left
}
in powersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
if (leftValue.inferType(program).isInteger) {
// times a power of two => shift left
val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
if (leftValue.inferType(program).isInteger) {
// times a negative power of two => negate, then shift left
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
@ -622,15 +643,15 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val targetIDt = expr.left.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("unknown dt")
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
} else if (amount >= 8) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
@ -657,7 +678,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val idt = expr.left.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
when (idt.typeOrElse(DataType.STRUCT)) {
when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)
@ -677,7 +698,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
// mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
}
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)

View File

@ -0,0 +1,82 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.InferredTypes
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this)
if (errors.noErrors()) {
replacer.applyModifications()
valuetypefixer.visit(this)
if(errors.noErrors()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
optimizer.visit(this)
while (errors.noErrors() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if (errors.noErrors()) {
replacer.visit(this)
replacer.applyModifications()
}
}
}
}
if(errors.noErrors())
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
}
fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget
): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizationCount
}
fun Program.simplifyExpressions() : Int {
val opti = ExpressionSimplifier(this)
opti.visit(this)
return opti.applyModifications()
}
fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, options, compTarget)
opti.visit(this)
return opti.applyModifications()
}
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt.istype(DataType.UBYTE) -> listOf("cx16", "r9L")
dt.istype(DataType.BYTE) -> listOf("cx16", "r9sL")
dt.istype(DataType.UWORD) -> listOf("cx16", "r9")
dt.istype(DataType.WORD) -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}

View File

@ -1,91 +1,72 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.*
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.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import kotlin.math.floor
internal class StatementOptimizer(private val program: Program,
class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
private val compTarget: ICompilationTarget
) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program, asmFileLoader)
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with a simple value,
// remove the jump altogeter and inline the returnvalue directly.
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
val target = variable.targetStatement(program) as INamedStatement
return IdentifierReference(target.scopedName, variable.position)
}
if (block !in callgraph.usedSymbols) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value?.isSimple==true) {
val copy = when(val orig = first.value!!) {
is AddressOf -> {
val scoped = scopePrefix(orig.identifier)
AddressOf(scoped, orig.position)
}
is DirectMemoryRead -> {
when(val expr = orig.addressExpression) {
is NumericLiteralValue -> DirectMemoryRead(expr.copy(), orig.position)
else -> return noModifications
}
}
is IdentifierReference -> scopePrefix(orig)
is NumericLiteralValue -> orig.copy()
is StringLiteralValue -> orig.copy()
else -> return noModifications
}
return listOf(IAstModification.ReplaceNode(functionCall, copy, parent))
}
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.definingScope())
}.toMutableList()
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
return removals
}
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
if(!subroutine.isAsmSubroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionStatementPlaceholder) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// only do this optimization if the arg is a known-constant string literal instead of a user defined variable.
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
@ -93,33 +74,32 @@ internal class StatementOptimizer(private val program: Program,
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program)!!
val string = vardecl.value as? StringLiteralValue
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
@ -132,33 +112,35 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
// see if we can optimize any complex arguments
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
val name = getTempVarName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
)
}
}
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
@ -186,20 +168,20 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
@ -216,7 +198,7 @@ internal class StatementOptimizer(private val program: Program,
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
@ -270,7 +252,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
}
}
return noModifications
@ -279,14 +261,14 @@ internal class StatementOptimizer(private val program: Program,
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@ -297,12 +279,13 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope()))
if(!jump.isGosub) {
// if the jump is to the next statement, remove the jump
val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program)
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
return listOf(IAstModification.Remove(jump, scope))
}
return noModifications
}
@ -325,27 +308,27 @@ internal class StatementOptimizer(private val program: Program,
if(rNum!=null) {
if (op1 == "+" || op1 == "-") {
if (op2 == "+") {
// A = A +/- B + N
// A = A +/- B + N ---> A = A +/- B ; A = A + N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val addConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") {
// A = A +/- B - N
// A = A +/- B - N ---> A = A +/- B ; A = A - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val subConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
}
}
}
@ -367,7 +350,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
val targetIDt = assignment.target.inferType(program)
@ -375,56 +358,68 @@ internal class StatementOptimizer(private val program: Program,
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) {
if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
return listOf(
IAstModification.ReplaceNode(bexpr, negation, assignment),
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
)
}
}
if (rightCv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
}
}
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
decs.statements.add(PostIncrDecr(assignment.target.copy(), "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"<<" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
">>" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
}
@ -435,23 +430,24 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
val subr = returnStmt.definingSubroutine!!
val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) {
// first assign to intermediary, then return that register
val returnValueIntermediary =
when(returnDt) {
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
else -> throw FatalAstException("weird return dt")
}
// first assign to intermediary variable, then return that
val returnVarName = "retval_interm_" + when(returnDt) {
DataType.UBYTE -> "ub"
DataType.BYTE -> "b"
DataType.UWORD -> "uw"
DataType.WORD -> "w"
else -> "<undefined>"
}
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
}
@ -460,12 +456,12 @@ internal class StatementOptimizer(private val program: Program,
when(returnStmt.value) {
is PrefixExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
is BinaryExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
@ -475,7 +471,7 @@ internal class StatementOptimizer(private val program: Program,
return super.after(returnStmt, parent)
}
private fun hasBreak(scope: INameScope): Boolean {
private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{

View File

@ -0,0 +1,247 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CallGraph
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isIOAddress
class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
): AstWalker() {
private val callgraph = CallGraph(program)
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
listOf(IAstModification.Remove(module, parent as IStatementContainer))
else
noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
if(!jump.isGosub)
reportUnreachable(jump)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement)
return emptyList()
}
private fun reportUnreachable(stmt: Statement) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
}
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return deduplicateAssignments(scope.statements, scope)
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
return deduplicateAssignments(block.statements, block)
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock.options()
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
if(callgraph.unused(subroutine)) {
if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
callgraph.calledBy[subroutine]?.let {
for(node in it)
removals.add(IAstModification.Remove(node, node.parent as IStatementContainer))
}
return removals
}
if(!subroutine.definingModule.isLibrary)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
}
}
return deduplicateAssignments(subroutine.statements, subroutine)
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock.options()
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
val usages = callgraph.usages(decl)
if (usages.isEmpty()) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} else {
// if all usages are just an assignment to this vardecl,
// and it is in regular RAM, then remove the var as well including all assignments
val assignTargets = usages.mapNotNull {
it.parent as? AssignTarget
}.filter {
!it.isIOAddress(compTarget.machine)
}
if(assignTargets.size==usages.size) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
return assignmentsToRemove.map {
IAstModification.Remove(it.first, it.second)
} + listOf(
IAstModification.Remove(decl, parent as IStatementContainer)
)
}
}
}
}
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
val modifications = mutableListOf<IAstModification>()
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.left isSameAs assign2.target) {
// X = X <oper> Right
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.left, zero, expr
))
}
if(expr.right isSameAs assign2.target) {
// X = Left <oper> X
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.right, zero, expr
))
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs assign2.target) {
// X = (X <oper> Right) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.left, zero, leftBinExpr
))
}
if(leftBinExpr.right isSameAs assign2.target) {
// X = (Left <oper> X) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.right, zero, leftBinExpr
))
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs assign2.target) {
// X = Something <oper> (X <oper> Right)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.left, zero, rightBinExpr
))
}
if(rightBinExpr.right isSameAs assign2.target) {
// X = Something <oper> (Left <oper> X)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.right, zero, rightBinExpr
))
}
}
}
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
}
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
val subCast = expr.expression as? TypecastExpression
if(subCast!=null && subCast.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
subCast.expression, zero, subCast
))
}
}
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null) {
val cvalue1 = assign1.value.constValue(program)
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
val value2 = assign2.value
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
when(value2) {
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
is TypecastExpression -> substituteZeroInTypecast(value2, zero, assign1, assign2)
else -> {}
}
} else {
if (assign1.target.isSameAs(assign2.target, program) && !assign1.target.isIOAddress(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(assign2.target.identifier!!.nameInSource))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
}
}
}
}
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
}
}

View File

@ -1,49 +1,55 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.1.0'
id "io.kotest" version "0.3.8"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
// implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
jvmTarget = javaVersion
}
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':compilerInterfaces')
implementation project(':codeOptimizers')
implementation project(':compilerAst')
implementation project(':codeGeneration')
implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
@ -58,7 +64,8 @@ sourceSets {
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
srcDir "${project.projectDir}/test"
srcDir "${project(':compilerAst').projectDir}/test/helpers"
}
}
}
@ -67,15 +74,9 @@ startScripts.enabled = true
application {
mainClass = 'prog8.CompilerMainKt'
mainClassName = 'prog8.CompilerMainKt' // deprecated
applicationName = 'p8compile'
}
artifacts {
archives shadowJar
}
shadowJar {
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
@ -96,12 +97,4 @@ test {
}
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}
build.finalizedBy installDist, installShadowDist

View File

@ -8,11 +8,17 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="codeGeneration" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>

Binary file not shown.

Binary file not shown.

View File

@ -428,7 +428,9 @@ var_fac1_greater_f .proc
cmp #1
beq +
lda #0
+ rts
rts
+ lda #1
rts
.pend
var_fac1_greatereq_f .proc

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%option enable_floats
floats {
@ -13,6 +12,7 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- C64 basic and kernal ROM float constants and functions ----
@ -83,7 +83,7 @@ romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
@ -195,7 +195,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -1,8 +1,7 @@
%target c64
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
@ -34,36 +33,36 @@ graphics {
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO there are some slight errors at the first/last pixels in certain slopes...??
; TODO implement this as optimized assembly, for instance https://github.com/EgonOlsen71/bresenham/blob/main/src/asm/graphics.asm ??
; or from here https://retro64.altervista.org/blog/an-introduction-to-vector-based-graphics-the-commodore-64-rotating-simple-3d-objects/
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as ubyte)
vertical_line(x1, y1, abs(dy) as ubyte +1)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword)
horizontal_line(x1, y1, abs(dx) as uword +1)
return
}
; TODO rewrite the rest in optimized assembly
word @zp d = 0
ubyte positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
word @zp dx2 = dx*2
word @zp dy2 = dy*2
internal_plotx = x1
if dx >= dy {
@ -73,10 +72,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx++
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
} else {
@ -85,10 +84,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx--
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
}
@ -100,10 +99,10 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx++
d -= dy
d -= dy2
}
}
} else {
@ -112,10 +111,10 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx--
d -= dy
d -= dy2
}
}
}

View File

@ -5,8 +5,6 @@
;
; indent format: TABS, size=8
%target c64
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
@ -17,7 +15,9 @@ c64 {
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
@ -202,7 +202,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -211,10 +211,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@ -246,7 +246,7 @@ asmsub STOP2() -> ubyte @A {
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
@ -478,7 +478,7 @@ sys {
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
lda #14
@ -489,6 +489,7 @@ sys {
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
@ -497,6 +498,27 @@ sys {
}
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
- bit c64.SCROLY
bmi -
rts
}}
}
inline asmsub waitrastborder() {
; --- busy wait till the raster position has reached the bottom screen border (approximately)
; note: a more accurate way to do this is by using a raster irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
@ -577,62 +599,34 @@ _longcopy
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
pla
tay
pla
tax
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}
@ -679,4 +673,88 @@ cx16 {
&uword r14 = $cf1c
&uword r15 = $cf1e
&word r0s = $cf00
&word r1s = $cf02
&word r2s = $cf04
&word r3s = $cf06
&word r4s = $cf08
&word r5s = $cf0a
&word r6s = $cf0c
&word r7s = $cf0e
&word r8s = $cf10
&word r9s = $cf12
&word r10s = $cf14
&word r11s = $cf16
&word r12s = $cf18
&word r13s = $cf1a
&word r14s = $cf1c
&word r15s = $cf1e
&ubyte r0L = $cf00
&ubyte r1L = $cf02
&ubyte r2L = $cf04
&ubyte r3L = $cf06
&ubyte r4L = $cf08
&ubyte r5L = $cf0a
&ubyte r6L = $cf0c
&ubyte r7L = $cf0e
&ubyte r8L = $cf10
&ubyte r9L = $cf12
&ubyte r10L = $cf14
&ubyte r11L = $cf16
&ubyte r12L = $cf18
&ubyte r13L = $cf1a
&ubyte r14L = $cf1c
&ubyte r15L = $cf1e
&ubyte r0H = $cf01
&ubyte r1H = $cf03
&ubyte r2H = $cf05
&ubyte r3H = $cf07
&ubyte r4H = $cf09
&ubyte r5H = $cf0b
&ubyte r6H = $cf0d
&ubyte r7H = $cf0f
&ubyte r8H = $cf11
&ubyte r9H = $cf13
&ubyte r10H = $cf15
&ubyte r11H = $cf17
&ubyte r12H = $cf19
&ubyte r13H = $cf1b
&ubyte r14H = $cf1d
&ubyte r15H = $cf1f
&byte r0sL = $cf00
&byte r1sL = $cf02
&byte r2sL = $cf04
&byte r3sL = $cf06
&byte r4sL = $cf08
&byte r5sL = $cf0a
&byte r6sL = $cf0c
&byte r7sL = $cf0e
&byte r8sL = $cf10
&byte r9sL = $cf12
&byte r10sL = $cf14
&byte r11sL = $cf16
&byte r12sL = $cf18
&byte r13sL = $cf1a
&byte r14sL = $cf1c
&byte r15sL = $cf1e
&byte r0sH = $cf01
&byte r1sH = $cf03
&byte r2sH = $cf05
&byte r3sH = $cf07
&byte r4sH = $cf09
&byte r5sH = $cf0b
&byte r6sH = $cf0d
&byte r7sH = $cf0f
&byte r8sH = $cf11
&byte r9sH = $cf13
&byte r10sH = $cf15
&byte r11sH = $cf17
&byte r12sH = $cf19
&byte r13sH = $cf1b
&byte r14sH = $cf1d
&byte r15sH = $cf1f
}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%import syslib
%import conv

View File

@ -7,239 +7,213 @@ conv {
; ----- number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
str string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
eor #255
clc
adc #1
+ jmp ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
_output_byte_digits
; hundreds?
cpy #'0'
beq +
pha
and #$0f
tax
ldy _hex_digits,x
tya
ldy P8ZP_SCRATCH_B1
sta string_out,y
pla
lsr a
lsr a
lsr a
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
inc P8ZP_SCRATCH_B1
; tens?
+ ldy P8ZP_SCRATCH_B1
cmp #'0'
beq +
sta string_out,y
iny
+ ; ones.
txa
sta string_out,y
iny
lda #0
sta string_out,y
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr ubyte2hex
sta output
sty output+1
lda P8ZP_SCRATCH_REG
jsr ubyte2hex
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
bpl +
pha
lda #'-'
sta string_out
inc P8ZP_SCRATCH_B1
pla
+ jsr conv.byte2decimal
bra str_ub._output_byte_digits
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
rts
}}
}
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in binary string form
%asm {{
sta P8ZP_SCRATCH_B1
ldy #0
sty string_out+8
ldy #7
- lsr P8ZP_SCRATCH_B1
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bpl -
rts
}}
}
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in binary string form
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr str_ubbin
ldy #0
sty string_out+16
ldy #7
- lsr P8ZP_SCRATCH_REG
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bpl -
rts
}}
}
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
%asm {{
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
rts
}}
}
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
ldx P8ZP_SCRATCH_REG
rts
_allzero lda #'0'
sta string_out,x
inx
bne _end
}}
}
asmsub str_w (word value @ AY) clobbers(A,Y) {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
%asm {{
cpy #0
bpl str_uw
stx P8ZP_SCRATCH_REG
pha
lda #'-'
sta string_out
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jsr conv.uword2decimal
ldx #1
bne str_uw._output_digits
}}
}
@ -520,4 +494,243 @@ _stop
}}
}
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
eor #255
clc
adc #1
+ jmp ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx P8ZP_SCRATCH_REG
pha
and #$0f
tax
ldy _hex_digits,x
pla
lsr a
lsr a
lsr a
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr ubyte2hex
sta output
sty output+1
lda P8ZP_SCRATCH_REG
jsr ubyte2hex
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}}
}
}

View File

@ -0,0 +1,38 @@
; Cx16 specific disk drive I/O routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
%import diskio
cx16diskio {
; Use kernal LOAD routine to load the given program file in memory.
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
; you'll have to compensate yourself by checking the ram banks).
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank)
uword size = diskio.load(drivenumber, filenameptr, address_override)
if size
return size + $2000 * (cx16.getrambank() - bank)
return 0
}
; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; This is different from Basic's LOAD instruction which always skips the first two bytes.
; The load address is mandatory. Returns the number of bytes loaded.
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
; you'll have to compensate yourself by checking the ram banks).
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
cx16.rambank(bank)
uword size = diskio.load_raw(drivenumber, filenameptr, address)
if size
return size + $2000 * (cx16.getrambank() - bank)
return 0
}
}

View File

@ -4,15 +4,17 @@
;
; indent format: TABS, size=8
%target cx16
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
; ---- this block contains C-64 compatible floating point related functions ----
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----
@ -43,46 +45,44 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
; note: there is no FPWR() on the Cx16
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $fea2 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
phx
sta P8ZP_SCRATCH_W2
sta _tmp
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
ldy _tmp
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
@ -91,6 +91,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
jsr FADD
+ plx
rts
_tmp .byte 0
_flt65536 .byte 145,0,0,0,0 ; 65536.0
}}
}
@ -128,6 +129,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
}}
}
asmsub FREADUY (ubyte value @Y) {
; -- 8 bit unsigned Y -> float in fac1
%asm {{
lda #0
jmp GIVAYF
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{
@ -149,7 +158,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -1,5 +1,3 @@
%target cx16
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
@ -22,6 +20,7 @@
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 {
@ -36,7 +35,7 @@ gfx2 {
sub screen_mode(ubyte mode) {
when mode {
1 -> {
; lores monchrome
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
@ -274,9 +273,9 @@ _done
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne +
bne ++
inc cx16.VERA_ADDR_L
bne +
bne ++
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
@ -292,92 +291,56 @@ _done
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y)
when active_mode {
1, 5 -> {
; monochrome, either resolution
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=5
cx16.r14 = 640/8
else
cx16.r14 = 320/8
; monochrome, lo-res
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if color {
if monochrome_dont_stipple_flag {
; draw continuous line.
position2(x,y,true)
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; stippling.
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{
lda x
eor y
and #1
bne +
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
+
asl cx16.r14
ldy height
beq +
- lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next-next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
dey
bne -
+
}}
; draw stippled line.
if x&1 {
y++
height--
}
position2(x,y,true)
if active_mode==1
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat height/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
cx16.r15 = ~cx16.r15
position2(x,y,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0
; sta cx16.VERA_ADDR_H
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
@ -385,6 +348,7 @@ _done
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(x,y)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy height
@ -398,83 +362,78 @@ _done
}
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; TODO optimize the loop in pure assembly
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if height==0
return
position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
%asm {{
; 24 bits add 160 (640/4)
clc
lda cx16.r0
adc #640/4
sta cx16.r0
lda cx16.r0+1
adc #0
sta cx16.r0+1
bcc +
inc cx16.r1
+
lda cx16.VERA_DATA0
and mask
ora color
sta cx16.VERA_DATA1
}}
}
}
}
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO there are some slight errors at the first/last pixels in certain slopes...
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = x2-x1 as word
word @zp dy = y2-y1 as word
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as uword, color)
vertical_line(x1, y1, abs(dy) as uword +1, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
horizontal_line(x1, y1, abs(dx) as uword +1, color)
return
}
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
word @zp d = 0
ubyte positive_ix = true
cx16.r13 = true ; 'positive_ix'
if dx < 0 {
dx = -dx
positive_ix = false
cx16.r13 = false
}
dx *= 2
dy *= 2
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if positive_ix {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14++
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
} else {
@ -483,25 +442,25 @@ _done
if cx16.r14==x2
return
cx16.r14--
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
}
}
else {
if positive_ix {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
cx16.r14++
d -= dy
d -= dy2
}
}
} else {
@ -510,10 +469,10 @@ _done
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
cx16.r14--
d -= dy
d -= dy2
}
}
}
@ -589,8 +548,6 @@ _done
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
uword addr
ubyte value
when active_mode {
1 -> {
@ -602,23 +559,43 @@ _done
and #1
}}
if_nz {
addr = x/8 + y*(320/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
; TODO mode 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
color = cx16.VERA_DATA0
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda color
sta cx16.VERA_DATA0
}}
}
5 -> {
; highres monochrome
@ -629,26 +606,48 @@ _done
and #1
}}
if_nz {
addr = x/8 + y*(640/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
color &= 3
color <<= shift4c[lsb(x) & 3]
; TODO optimize the vera memory manipulation in pure assembly
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
color <<= shift4c[cx16.r2L]
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
ldy cx16.r2L ; xbits
lda mask4c,y
and cx16.VERA_DATA0
ora color
sta cx16.VERA_DATA0
}}
}
}
}
@ -682,6 +681,20 @@ _done
}
}
sub position2(uword @zp x, uword y, ubyte also_port_1) {
position(x, y)
if also_port_1 {
when active_mode {
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
; TODO modes 2, 3
4, 6 -> {
ubyte bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 1, 1)
}
}
}
}
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
@ -761,13 +774,14 @@ _done
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
uword chardataptr
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode>=5
if active_mode==5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
@ -795,11 +809,8 @@ _done
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ lda x
clc
adc #1
sta x
bcc +
+ inc x
bne +
inc x+1
+ dey
bne -
@ -813,7 +824,6 @@ _done
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
@ -842,7 +852,9 @@ _done
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
; TODO rewrite this inner loop partly in assembly
; requires expanding the charbits to 2-bits per pixel (based on color)
; also it's way more efficient to draw whole horizontal spans instead of per-character
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1

View File

@ -1,10 +1,9 @@
%target cx16
%import syslib
%import textio
; Bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM.
; only black/white monchrome 320x200 for now. (i.e. truncated at the bottom)
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.

View File

@ -1,5 +1,3 @@
%target cx16
; Manipulate the Commander X16's display color palette.
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
@ -9,7 +7,7 @@ palette {
ubyte c
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+index*2
vera_palette_ptr = $fa00+(index as uword * 2)
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
@ -70,6 +68,14 @@ palette {
}
}
sub set_all_black() {
set_monochrome($000, $000)
}
sub set_all_white() {
set_monochrome($fff, $fff)
}
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {

View File

@ -5,9 +5,6 @@
;
; indent format: TABS, size=8
%target cx16
c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
@ -24,7 +21,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
@ -35,7 +32,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -44,10 +41,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@ -74,7 +71,7 @@ asmsub STOP2() -> ubyte @A {
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
phx
jsr c64.RDTIM
@ -87,29 +84,20 @@ asmsub RDTIM16() -> uword @AY {
}}
}
asmsub MEMTOP2() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
}
cx16 {
; irq and hardware vectors:
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
&uword r0 = $0002
&uword r1 = $0004
&uword r2 = $0006
@ -127,6 +115,91 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
&word r3s = $0008
&word r4s = $000a
&word r5s = $000c
&word r6s = $000e
&word r7s = $0010
&word r8s = $0012
&word r9s = $0014
&word r10s = $0016
&word r11s = $0018
&word r12s = $001a
&word r13s = $001c
&word r14s = $001e
&word r15s = $0020
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
&ubyte r3L = $0008
&ubyte r4L = $000a
&ubyte r5L = $000c
&ubyte r6L = $000e
&ubyte r7L = $0010
&ubyte r8L = $0012
&ubyte r9L = $0014
&ubyte r10L = $0016
&ubyte r11L = $0018
&ubyte r12L = $001a
&ubyte r13L = $001c
&ubyte r14L = $001e
&ubyte r15L = $0020
&ubyte r0H = $0003
&ubyte r1H = $0005
&ubyte r2H = $0007
&ubyte r3H = $0009
&ubyte r4H = $000b
&ubyte r5H = $000d
&ubyte r6H = $000f
&ubyte r7H = $0011
&ubyte r8H = $0013
&ubyte r9H = $0015
&ubyte r10H = $0017
&ubyte r11H = $0019
&ubyte r12H = $001b
&ubyte r13H = $001d
&ubyte r14H = $001f
&ubyte r15H = $0021
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
&byte r3sL = $0008
&byte r4sL = $000a
&byte r5sL = $000c
&byte r6sL = $000e
&byte r7sL = $0010
&byte r8sL = $0012
&byte r9sL = $0014
&byte r10sL = $0016
&byte r11sL = $0018
&byte r12sL = $001a
&byte r13sL = $001c
&byte r14sL = $001e
&byte r15sL = $0020
&byte r0sH = $0003
&byte r1sH = $0005
&byte r2sH = $0007
&byte r3sH = $0009
&byte r4sH = $000b
&byte r5sH = $000d
&byte r6sH = $000f
&byte r7sH = $0011
&byte r8sH = $0013
&byte r9sH = $0015
&byte r10sH = $0017
&byte r11sH = $0019
&byte r12sH = $001b
&byte r13sH = $001d
&byte r14sH = $001f
&byte r15sH = $0021
; VERA registers
const uword VERA_BASE = $9F20
@ -172,7 +245,7 @@ cx16 {
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
const uword via1 = $9f00 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
@ -190,7 +263,7 @@ cx16 {
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
const uword via2 = $9f10 ;VIA 6522 #2
&ubyte d2prb = via2+0
&ubyte d2pra = via2+1
&ubyte d2ddrb = via2+2
@ -208,6 +281,11 @@ cx16 {
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40
&ubyte ym2151dat = $9f41
const uword extdev = $9f60
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
@ -236,7 +314,8 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
; high level graphics & fonts
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
@ -291,19 +370,42 @@ romsub $fecc = monitor() clobbers(A,X,Y)
; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
inline asmsub rombank(ubyte bank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (new)
sta cx16.d1prb ; rom bank register (old)
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub rambank(ubyte rambank @A) {
inline asmsub rambank(ubyte bank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (new)
sta cx16.d1pra ; ram bank register (old)
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
}}
}
inline asmsub getrombank() -> ubyte @A {
; -- get the current rom bank
%asm {{
lda $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub getrambank() -> ubyte @A {
; -- get the current ram bank
%asm {{
lda $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
}}
}
asmsub numbanks() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
@ -350,75 +452,126 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
}
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into video memory in the given bank:address, returns success in A
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
; it works fine when loading from local filesystem
%asm {{
; -- load a file into video ram
phx
pha
tya
tax
lda #1
ldy #0
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
jsr prog8_lib.strlen
tya
ldx cx16.r0
ldy cx16.r0+1
jsr c64.SETNAM
pla
clc
adc #2
ldx cx16.r1
ldy cx16.r1+1
stz P8ZP_SCRATCH_B1
jsr c64.LOAD
bcs +
inc P8ZP_SCRATCH_B1
+ jsr c64.CLRCHN
lda #1
jsr c64.CLOSE
plx
lda P8ZP_SCRATCH_B1
rts
}}
}
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
%asm {{
sei
jsr cx16.joystick_get
cli
}}
}
sub FB_set_pixels_from_buf(uword buffer, uword count) {
%asm {{
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
@ -460,11 +613,9 @@ asmsub init_system() {
%asm {{
sei
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80
sta VERA_CTRL
stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
@ -664,23 +815,37 @@ sys {
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
stz $01 ; bank the kernal in (new rom bank register)
stz cx16.d1prb ; bank the kernal in (old rom bank register)
stz $01 ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs
%asm {{
- wai ; wait for irq (assume it was vsync)
cmp #0
bne +
dey
+ dec a
bne -
cpy #0
bne -
rts
}}
}
inline asmsub waitvsync() {
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
wai
}}
}
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
@ -711,27 +876,6 @@ sys {
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
pha
phy
phx
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
plx
ply
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@ -741,25 +885,25 @@ sys {
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target cx16
%import syslib
%import conv
@ -420,7 +419,7 @@ _print_byte_digits
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
bra _ones
+ pla
cmp #'0'
beq _ones
@ -443,7 +442,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
bra print_ub._print_byte_digits
}}
}
@ -494,7 +493,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubbin
pla
clc
jmp print_ubbin
bra print_ubbin
}}
}
@ -507,7 +506,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubhex
pla
clc
jmp print_ubhex
bra print_ubhex
}}
}
@ -566,11 +565,10 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
tay
pla
eor #255
clc
adc #1
bcc +
ina
bne +
iny
+ jmp print_uw
+ bra print_uw
}}
}

View File

@ -336,6 +336,45 @@ _end rts
}
; ----- iterative file saver functions (uses io channel 14) -----
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
; -- open a file for iterative writing with f_write
f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename"
if_cc {
void c64.CHKOUT(14) ; use #14 as input channel
return not c64.READST()
}
f_close_w()
return false
}
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
; -- write the given umber of bytes to the currently open file
if num_bytes!=0 {
void c64.CHKOUT(14) ; use #14 as input channel again
repeat num_bytes {
c64.CHROUT(@(bufferpointer))
bufferpointer++
}
return not c64.READST()
}
return true
}
sub f_close_w() {
; -- end an iterative file writing session (close channels).
c64.CLRCHN()
c64.CLOSE(14)
}
; ---- other functions ----
sub status(ubyte drivenumber) -> uword {
; -- retrieve the disk drive's current status message
uword messageptr = &filename
@ -353,18 +392,22 @@ _end rts
messageptr++
}
io_error:
@(messageptr) = 0
done:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
return filename
}
io_error:
filename = "?disk error"
goto done
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
first_byte = 0 ; result var reuse
%asm {{
lda address
@ -381,7 +424,6 @@ io_error:
plp
}}
first_byte = 0 ; result var reuse
if_cc
first_byte = c64.READST()==0
@ -391,6 +433,16 @@ io_error:
return first_byte
}
; Use kernal LOAD routine to load the given program file in memory.
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the number of bytes loaded.
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load() instead.
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
@ -414,13 +466,32 @@ io_error:
c64.CLOSE(1)
if end_of_load
return end_of_load - address_override
return end_of_load - address_override ; not correct when the file spans multiple RAM banks
return 0
}
str filename = "0:??????????????????????????????????????"
; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; This is different from Basic's LOAD instruction which always skips the first two bytes.
; The load address is mandatory. Returns the number of bytes loaded.
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load_raw() instead.
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
if not f_open(drivenumber, filenameptr)
return 0
uword size = f_read(address, 2)
f_close()
if size!=2
return 0
address += 2
size = load(drivenumber, filenameptr, address)
if size
return size+2
return 0
}
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
@ -447,4 +518,6 @@ io_error:
c64.CLRCHN()
c64.CLOSE(1)
}
str filename = "0:??????????????????????????????????????"
}

View File

@ -244,68 +244,45 @@ randseed .proc
.pend
randbyte .proc
; -- 8-bit pseudo random number generator into A
lda _seed
beq _eor
asl a
beq _done ; if the input was $80, skip the EOR
bcc _done
_eor eor #$1d ; xor with magic value see below for possible values
_done sta _seed
rts
_seed .byte $3a
; possible 'magic' eor bytes are:
; $1d, $2b, $2d, $4d, $5f, $63, $65, $69
; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc
; -- 16 bit pseudo random number generator into AY
magic_eor = $3f1d
; possible magic eor words are:
; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
; rand64k ;Factors of 65535: 3 5 17 257
lda sr1+1
asl a
asl a
eor sr1+1
asl a
eor sr1+1
asl a
asl a
eor sr1+1
asl a
rol sr1 ;shift this left, "random" bit comes from low
rol sr1+1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
lda sr2+1
asl a
eor sr2+1
asl a
asl a
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
rol sr2+1
lda sr1+1 ;can be left out
eor sr2+1 ;if you dont use
tay ;y as suggested
lda sr1 ;mix up lowbytes of SR1
eor sr2 ;and SR2 to combine both
rts
lda _seed
beq _lowZero ; $0000 and $8000 are special values to test for
sr1 .word $a55a
sr2 .word $7653
; Do a normal shift
asl _seed
lda _seed+1
rol a
bcc _noEor
_doEor ; high byte is in A
eor #>magic_eor
sta _seed+1
lda _seed
eor #<magic_eor
sta _seed
ldy _seed+1
rts
_lowZero lda _seed+1
beq _doEor ; High byte is also zero, so apply the EOR
; For speed, you could store 'magic' into 'seed' directly
; instead of running the EORs
; wasn't zero, check for $8000
asl a
beq _noEor ; if $00 is left after the shift, then it was $80
bcs _doEor ; else, do the EOR based on the carry bit as usual
_noEor sta _seed+1
tay
lda _seed
rts
_seed .word $2c9e
.pend
@ -797,6 +774,13 @@ stack_mul_word_320 .proc
rts
.pend
stack_mul_word_640 .proc
; stackW = (stackLo * 2 * 320) (stackHi doesn't matter)
asl P8ESTACK_LO+1,x
jmp stack_mul_word_320
.pend
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
mul_byte_3 .proc
; A = A + A*2
@ -1287,6 +1271,13 @@ mul_word_320 .proc
rts
.pend
mul_word_640 .proc
; AY = (A * 2 * 320) (msb in Y doesn't matter)
asl a
jmp mul_word_320
.pend
; ----------- end optimized multiplications -----------
@ -1537,3 +1528,71 @@ _negative lsr a
rts
.pend
square .proc
; -- calculate square root of signed word in AY, result in AY
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
; using this routine is about twice as fast as doing a regular multiplication.
;
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
; Squarel/Squareh
;
; The maximum input range is only +/-255 and no checking is done to ensure that
; this is so.
;
; This routine is useful if you are trying to draw circles as for any circle
;
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
; r is the circle radius
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
squarel = P8ZP_SCRATCH_W2 ; square low byte
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
sta numberl
sty numberh
stx P8ZP_SCRATCH_REG
lda #$00 ; clear a
sta squarel ; clear square low byte
; (no need to clear the high byte, it gets shifted out)
lda numberl ; get number low byte
ldx numberh ; get number high byte
bpl _nonneg ; if +ve don't negate it
; else do a two's complement
eor #$ff ; invert
sec ; +1
adc #$00 ; and add it
_nonneg:
sta tempsq ; save abs(number)
ldx #$08 ; set bit count
_nextr2bit:
asl squarel ; low byte *2
rol squareh ; high byte *2+carry from low
asl a ; shift number byte
bcc _nosqadd ; don't do add if c = 0
tay ; save a
clc ; clear carry for add
lda tempsq ; get number
adc squarel ; add number^2 low byte
sta squarel ; save number^2 low byte
lda #$00 ; clear a
adc squareh ; add number^2 high byte
sta squareh ; save number^2 high byte
tya ; get a back
_nosqadd:
dex ; decrement bit count
bne _nextr2bit ; go do next bit
lda squarel
ldy squareh
ldx P8ZP_SCRATCH_REG
rts
.pend

View File

@ -3,5 +3,5 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
math {
%asminclude "library:math.asm", ""
%asminclude "library:math.asm"
}

View File

@ -91,6 +91,13 @@ func_sin8_into_A .proc
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sinr8_into_A .proc
tay
lda _sinecosR8,y
rts
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
.pend
func_sin8u_into_A .proc
tay
lda _sinecos8u,y
@ -98,6 +105,13 @@ func_sin8u_into_A .proc
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
.pend
func_sinr8u_into_A .proc
tay
lda _sinecosR8u,y
rts
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
.pend
func_sin8_stack .proc
tay
lda func_sin8_into_A._sinecos8,y
@ -106,6 +120,14 @@ func_sin8_stack .proc
rts
.pend
func_sinr8_stack .proc
tay
lda func_sinr8_into_A._sinecosR8,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_sin8u_stack .proc
tay
lda func_sin8u_into_A._sinecos8u,y
@ -114,18 +136,38 @@ func_sin8u_stack .proc
rts
.pend
func_sinr8u_stack .proc
tay
lda func_sinr8u_into_A._sinecosR8u,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_cos8_into_A .proc
tay
lda func_sin8_into_A._sinecos8+64,y
rts
.pend
func_cosr8_into_A .proc
tay
lda func_sinr8_into_A._sinecosR8+45,y
rts
.pend
func_cos8u_into_A .proc
tay
lda func_sin8u_into_A._sinecos8u+64,y
rts
.pend
func_cosr8u_into_A .proc
tay
lda func_sinr8u_into_A._sinecosR8u+45,y
rts
.pend
func_cos8_stack .proc
tay
lda func_sin8_into_A._sinecos8+64,y
@ -134,6 +176,14 @@ func_cos8_stack .proc
rts
.pend
func_cosr8_stack .proc
tay
lda func_sinr8_into_A._sinecosR8+45,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_cos8u_stack .proc
tay
lda func_sin8u_into_A._sinecos8u+64,y
@ -142,6 +192,14 @@ func_cos8u_stack .proc
rts
.pend
func_cosr8u_stack .proc
tay
lda func_sinr8u_into_A._sinecosR8u+45,y
sta P8ESTACK_LO,x
dex
rts
.pend
func_sin16_into_AY .proc
tay
lda _sinecos8lo,y
@ -155,6 +213,19 @@ _sinecos8lo .byte <_
_sinecos8hi .byte >_
.pend
func_sinr16_into_AY .proc
tay
lda _sinecosR8lo,y
pha
lda _sinecosR8hi,y
tay
pla
rts
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
_sinecosR8lo .byte <_
_sinecosR8hi .byte >_
.pend
func_sin16u_into_AY .proc
tay
lda _sinecos8ulo,y
@ -168,6 +239,18 @@ _sinecos8ulo .byte <_
_sinecos8uhi .byte >_
.pend
func_sinr16u_into_AY .proc
tay
lda _sinecosR8ulo,y
pha
lda _sinecosR8uhi,y
tay
pla
rts
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
_sinecosR8ulo .byte <_
_sinecosR8uhi .byte >_
.pend
func_sin16_stack .proc
tay
@ -179,6 +262,16 @@ func_sin16_stack .proc
rts
.pend
func_sinr16_stack .proc
tay
lda func_sinr16_into_AY._sinecosR8lo,y
sta P8ESTACK_LO,x
lda func_sinr16_into_AY._sinecosR8hi,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_sin16u_stack .proc
tay
lda func_sin16u_into_AY._sinecos8ulo,y
@ -189,6 +282,16 @@ func_sin16u_stack .proc
rts
.pend
func_sinr16u_stack .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo,y
sta P8ESTACK_LO,x
lda func_sinr16u_into_AY._sinecosR8uhi,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_cos16_into_AY .proc
tay
lda func_sin16_into_AY._sinecos8lo+64,y
@ -199,6 +302,16 @@ func_cos16_into_AY .proc
rts
.pend
func_cosr16_into_AY .proc
tay
lda func_sinr16_into_AY._sinecosR8lo+45,y
pha
lda func_sinr16_into_AY._sinecosR8hi+45,y
tay
pla
rts
.pend
func_cos16u_into_AY .proc
tay
lda func_sin16u_into_AY._sinecos8ulo+64,y
@ -209,6 +322,16 @@ func_cos16u_into_AY .proc
rts
.pend
func_cosr16u_into_AY .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
pha
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
tay
pla
rts
.pend
func_cos16_stack .proc
tay
lda func_sin16_into_AY._sinecos8lo+64,y
@ -219,6 +342,16 @@ func_cos16_stack .proc
rts
.pend
func_cosr16_stack .proc
tay
lda func_sinr16_into_AY._sinecosR8lo+45,y
sta P8ESTACK_LO,x
lda func_sinr16_into_AY._sinecosR8hi+45,y
sta P8ESTACK_HI,x
dex
rts
.pend
func_cos16u_stack .proc
tay
lda func_sin16u_into_AY._sinecos8ulo+64,y
@ -229,6 +362,16 @@ func_cos16u_stack .proc
rts
.pend
func_cosr16u_stack .proc
tay
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
sta P8ESTACK_LO,x
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
sta P8ESTACK_HI,x
dex
rts
.pend
abs_b_stack .proc
; -- push abs(A) on stack (as byte)
jsr abs_b_into_A
@ -432,6 +575,7 @@ func_min_ub_stack .proc
func_min_b_into_A .proc
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #127
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y
@ -548,6 +692,7 @@ func_min_w_stack .proc
func_max_ub_into_A .proc
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #0
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y

View File

@ -1072,3 +1072,14 @@ sign_extend_AY_byte .proc
rts
.pend
strlen .proc
; -- returns the number of bytes in the string in AY, in Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ rts
.pend

View File

@ -3,13 +3,27 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
prog8_lib {
%asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
; to store intermediary expression results for return values:
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
uword @zp retval_interm_uw
word @zp retval_interm_w
ubyte @zp retval_interm_ub
byte @zp retval_interm_b
word retval_interm_w2
byte retval_interm_b2
; prog8 "hooks" to be able to access the temporary scratch variables
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
; NOTE: the assembly code generator will match these names and not generate
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
&ubyte P8ZP_SCRATCH_REG = $ff
&byte P8ZP_SCRATCH_B1 = $ff
&uword P8ZP_SCRATCH_W1 = $ff
&word P8ZP_SCRATCH_W2 = $ff
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{

View File

@ -178,7 +178,7 @@ _found sty P8ZP_SCRATCH_B1
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
@ -190,8 +190,8 @@ _found sty P8ZP_SCRATCH_B1
}}
}
asmsub lower(uword st @AY) {
; Lowercases the petscii string in-place.
asmsub lower(uword st @AY) -> ubyte @Y {
; Lowercases the petscii string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
%asm {{
@ -213,8 +213,8 @@ _done rts
}}
}
asmsub upper(uword st @AY) {
; Uppercases the petscii string in-place.
asmsub upper(uword st @AY) -> ubyte @Y {
; Uppercases the petscii string in-place. Returns length of the string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1

View File

@ -1 +1 @@
6.2
7.5

View File

@ -1,15 +1,13 @@
package prog8
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.multiple
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.parser.ParsingFailedError
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
@ -18,43 +16,60 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
printSoftwareHeader("compiler")
compileMain(args)
}
internal fun printSoftwareHeader(what: String) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
val succes = compileMain(args)
if(!succes)
exitProcess(1)
}
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) {
private fun compileMain(args: Array<String>): Boolean {
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
cli.parse(args)
} catch (e: IllegalStateException) {
System.err.println(e.message)
exitProcess(1)
return false
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
exitProcess(1)
return false
}
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption")
return false
}
val srcdirs = sourceDirs.toMutableList()
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
if(watchMode==true) {
@ -66,7 +81,18 @@ private fun compileMain(args: Array<String>) {
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
val compilerArgs = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
val compilationResult = compileProgram(compilerArgs)
results.add(compilationResult)
}
@ -77,7 +103,7 @@ private fun compileMain(args: Array<String>) {
for (importedFile in allImportedFiles) {
print(" ")
println(importedFile)
val watchDir = importedFile.parent ?: Path.of(".")
val watchDir = importedFile.parent ?: Path.of("")
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
@ -103,22 +129,39 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
val compilerArgs = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
compilationResult = compileProgram(compilerArgs)
if (!compilationResult.success)
return false
} catch (x: AstException) {
exitProcess(1)
return false
}
if (startEmulator==true) {
if (compilationResult.programName.isEmpty())
if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.programName.isEmpty()) {
println("\nCan't start emulator because no program was assembled.")
else {
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
return true
}
}
val programNameInPath = outputPath.resolve(compilationResult.programName)
if (startEmulator1==true)
compilationResult.compTarget.machine.launchEmulator(1, programNameInPath)
else if (startEmulator2==true)
compilationResult.compTarget.machine.launchEmulator(2, programNameInPath)
}
}
return true
}

View File

@ -1,3 +0,0 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -1,6 +1,7 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -8,27 +9,40 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
private fun rememberSubroutineVar(decl: VarDecl) {
val sub = decl.definingSubroutine ?: return
var varsList = subroutineVariables[sub]
if(varsList==null) {
varsList = mutableListOf()
subroutineVariables[sub] = varsList
}
varsList.add(decl.name to decl)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// move all subroutines to the bottom of the block
val subs = block.statements.filterIsInstance<Subroutine>()
block.statements.removeAll(subs)
block.statements.addAll(subs)
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
}
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
rememberSubroutineVar(decl)
return noModifications
}
@ -38,26 +52,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& compTarget.isInRegularRAM(assignment.target, program)) {
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
if(binExpr.operator in associativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@ -66,38 +88,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine()
if (sub != null) {
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
}
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
for(decl in rememberedSubroutineVars) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
@ -105,40 +105,43 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
firstDeclarations[decl.first] = decl.second
}
}
rememberedSubroutineVars.clear()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& !subroutine.inline
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
if (subroutine.asmAddress == null && !subroutine.inline) {
if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
&& subroutine.statements.last() !is Subroutine)) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerScope = subroutine.definingScope
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
if (subroutineStmtIdx > 0) {
val prevStmt = outerStatements[subroutineStmtIdx-1]
if(outerScope !is Block
&& (prevStmt !is Jump || prevStmt.isGosub)
&& prevStmt !is Subroutine
&& prevStmt !is Return) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
}
return mods
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// see if we can remove redundant typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
@ -146,32 +149,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
}
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall
if (fcall != null) {
val sub = fcall.target.targetStatement(program) as? Subroutine
if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
}
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
typecast.expression,
parent
))
))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
@ -187,33 +181,253 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications
}
@Suppress("DuplicatedCode")
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifStatement.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
return noModifications
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
}
return modifications
}
private class CondExprSimplificationResult(
val leftVarAssignment: Assignment?,
val leftOperandReplacement: Expression?,
val rightVarAssignment: Assignment?,
val rightOperandReplacement: Expression?
)
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
// TODO: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
// TODO: this should be replaced by a general expression-evaluation optimization step.
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
if(separateLeftExpr) {
val name = getTempVarName(expr.left.inferType(program))
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left,
expr.position
)
}
if(separateRightExpr) {
val dt = expr.right.inferType(program)
val name = when {
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")
dt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw")
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2")
dt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w2")
else -> throw AssemblyError("invalid dt")
}
rightOperandReplacement = IdentifierReference(name, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.right,
expr.position
)
}
return CondExprSimplificationResult(
leftAssignment, leftOperandReplacement,
rightAssignment, rightOperandReplacement
)
}
@Suppress("DuplicatedCode")
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = untilLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// until not x -> until x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
return noModifications
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.rightVarAssignment, untilLoop.body)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.leftVarAssignment, untilLoop.body)
}
return modifications
}
@Suppress("DuplicatedCode")
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = whileLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// while not x -> while x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// TODO simplify the conditional expression, introduce simple assignments if required.
// make sure to evaluate it only once, but also right at entry of the while loop
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
// so the expression needs to be evaluated also before the loop is entered...
// but I don't want to duplicate the expression.
// val simplify = simplifyConditionalExpression(binExpr)
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = functionCallStatement.args[0]
val arg2 = functionCallStatement.args[1]
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
} else {
if(dt2 in WordDatatypes)
return noModifications
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val containingStatement = getContainingStatement(arrayIndexedExpression)
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
return noModifications
}
val index = arrayIndexedExpression.indexer.indexExpr
if(index !is NumericLiteralValue && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first
return getAutoIndexerVarFor(arrayIndexedExpression)
}
return noModifications
}
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
class Searcher : IAstVisitor {
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val ix = arrayIndexedExpression.indexer.indexExpr
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
complexArrayIndexedExpressions.add(arrayIndexedExpression)
}
override fun visit(branchStatement: BranchStatement) {}
override fun visit(forLoop: ForLoop) {}
override fun visit(ifStatement: IfStatement) {
ifStatement.condition.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
untilLoop.condition.accept(this)
}
}
val searcher = Searcher()
stmt.accept(searcher)
return searcher.complexArrayIndexedExpressions
}
private fun getContainingStatement(expression: Expression): Statement {
var node: Node = expression
while(node !is Statement)
node = node.parent
return node
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program)
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b")
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}
}

Some files were not shown because too many files have changed in this diff Show More