Compare commits

...

182 Commits

Author SHA1 Message Date
12abafb917 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-06-12 00:11:58 +02:00
8dc2e47507 fix partial unused code removal in vm target 2025-06-11 23:31:29 +02:00
0be90dedf2 check for split word array as argument 2025-06-11 21:35:36 +02:00
daf7c3357c better detection of missing return statement
preparing 11.4
2025-06-09 16:01:56 +02:00
e6bab3ceeb IR: adding LOADFIELD and STOREFIELD instructions 2025-06-09 01:41:45 +02:00
59387b2ae8 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-06-07 20:28:23 +02:00
e8795859c5 added sorting library for target virtual
added sorting routines that sort a values array together with the keys array
optimized gnomesort a little
2025-06-07 19:42:40 +02:00
bebe60b687 fix compiler crash on for x in wordvar, add sys.get_as_returnaddress() 2025-06-05 16:10:40 +02:00
ddceec364e optimized coroutines library 2025-06-04 21:34:32 +02:00
f8f20440d3 allow pointer to be treated as uword value in augmented assignments 2025-06-03 21:33:35 +02:00
f8722faa4e Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-06-03 21:15:19 +02:00
d067fa4b73 added strings.find_eol() 2025-06-03 21:09:44 +02:00
26fbbf48a4 adapt new antlr visitor to the pointer/struct additions 2025-06-03 01:28:58 +02:00
d5cc414221 Merge branch 'master' into structs
# Conflicts:
#	codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-06-02 20:52:50 +02:00
b5e51ab937 cleaner timings output 2025-06-02 19:30:25 +02:00
552e55c29f fix missing cmp #0 when asmsub call is part of a boolean expression 2025-06-02 19:22:00 +02:00
a228908c1a fix wrong address calculation for &wordarray[i] where i is a variable 2025-06-02 03:13:23 +02:00
15fc3b6c04 replace old antlr2kotlin code with the new visitor-based translator 2025-06-02 01:56:07 +02:00
0456badd02 creating on a new visitor-based antlr to kotlin translator 2025-06-02 01:18:07 +02:00
d28f154f1c Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-06-01 17:53:41 +02:00
399cf5118d we will get a 11.4 version first before structs will land 2025-06-01 17:47:06 +02:00
a87f2640d3 fixed signed byte comparisons in case of overflowing values 2025-06-01 14:01:25 +02:00
a90ef274d7 fix word*128 codegen.
added cx16/landscape.p8 example that draws procedurally generated landscapes.
found bug in signed byte comparisons with overflow.
2025-05-31 05:27:19 +02:00
1c02179c5c refactor loadIndexReg() 2025-05-30 21:39:17 +02:00
77584493fd support a.b.ptr[i]^^.value as expression (RHS) 2025-05-30 20:28:10 +02:00
a36709e638 Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
2025-05-30 12:39:23 +02:00
341778ba67 added -timings flag 2025-05-30 12:38:16 +02:00
8d63cce749 working on deref after array indexing 2025-05-30 11:30:49 +02:00
ec50b5a007 homebrew info 2025-05-30 03:43:03 +02:00
8e7bbcdbe0 clear syntax error for yet unsupported deref after array indexing 2025-05-29 21:14:38 +02:00
37ecdc47b3 allow ptr1^^ = ptr^^ (replaces it with memcopy) 2025-05-29 16:34:47 +02:00
112ca3cc53 allow sizeof(&thing), add sys.SIZEOF_POINTER 2025-05-29 15:58:29 +02:00
33b3a1664c replace sizeof(list^^) with sizeof(List) to allow it to compile. Same with simple pointers. 2025-05-29 14:13:42 +02:00
8a0c02e264 Merge branch 'refs/heads/master' into structs
# Conflicts:
#	codeCore/src/prog8/code/target/NormalMemSizer.kt
#	codeCore/src/prog8/code/target/VMTarget.kt
#	compiler/src/prog8/compiler/BuiltinFunctions.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-05-29 13:42:16 +02:00
31d84c8921 doc 2025-05-29 13:35:20 +02:00
34bedbeef1 optimize byte modulus (%) routine with repeated subtraction instead of using full division 2025-05-29 13:26:04 +02:00
3b1b0985c1 make sizeof(float) work, so you don't have to use sys.SIZEOF_FLOAT anymore etc.
define sys.SIZEOF_FLOAT in terms of sizeof(float)
2025-05-29 12:38:03 +02:00
e40ace9dea todo 2025-05-29 01:35:53 +02:00
4c0e6e2640 fix split/nosplit pointer arrays, replace ubyteptr^^ with @(ubyteptr), fix double dereference 2025-05-29 00:36:55 +02:00
08b314c37d IR: fix various register type mismatches 2025-05-28 22:15:07 +02:00
86da9d3c7e assigning to plain pointer with array indexing 2025-05-28 18:08:53 +02:00
4e61e25c02 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
2025-05-27 23:52:59 +02:00
5097d52d99 IR codegen for pointer indexing expressions, -assignment 2025-05-27 23:41:08 +02:00
368387e1a7 allow floats to be (explicitly) cast to integers 2025-05-26 21:39:48 +02:00
09d2185bb1 PtArrayIndexer variable is now nullable (because it could be a ptr deref instead) 2025-05-25 23:04:32 +02:00
5c02e2bd71 fix a ptr indexing case, fix address-of fields 2025-05-25 21:32:31 +02:00
fb01389b3d cleaning up pointer deref 2025-05-25 18:33:37 +02:00
aaa81210ce cleaning up pointer indexing 2025-05-25 02:56:32 +02:00
51269257ea fix a.b.c.d desugaring into pointer deref chain 2025-05-24 14:48:02 +02:00
23a853db1e Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	examples/test.p8
2025-05-23 19:00:59 +02:00
9da430ffeb vm: more complete V-flag handling. somd doc and todo updates. 2025-05-23 18:58:14 +02:00
cc063124cf add joystick control to cx16 fileselector.
fix fileselector Basic exasmple.
fixed too aggressive asm peephole optimization that destroyed %jumptable in libraries for example.
2025-05-23 17:50:11 +02:00
3b37b89951 added cx16.joysticks_detect() and cx16.joysticks_getall() 2025-05-23 02:26:21 +02:00
844b537d1e cobramk3 example now draws with new monogfx doublebuffering 2025-05-22 23:29:49 +02:00
caf1d4a22a fix monogfx INVERT draw mode 2025-05-22 21:29:23 +02:00
d8e244df99 fix monogfx example 2025-05-22 00:37:20 +02:00
548e421e27 added doublebuffering to monogfx (in both lores and hires mode) 2025-05-22 00:10:03 +02:00
322fa7ea69 slightly optimize monogfx plot() 2025-05-21 01:16:43 +02:00
db6c887795 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/ast/TestVariousCompilerAst.kt
2025-05-21 00:27:45 +02:00
cf7bea0985 cleanup RTS insertion and ast postprocessing before assembly generation 2025-05-21 00:19:50 +02:00
61fe55168a Merge branch 'master' into structs
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
2025-05-20 22:35:04 +02:00
25d7f8808f IR: added signed multiplication opcodes 2025-05-20 21:36:05 +02:00
1c4999ec87 adding ptr unit tests 2025-05-20 20:57:05 +02:00
c726d3f937 fix ptr errors 2025-05-19 22:49:07 +02:00
f70341df1b fix ptr errors 2025-05-19 20:06:31 +02:00
f0b791452e fix many ptr deref errors 2025-05-19 01:33:57 +02:00
adf5600a9b simplify 2025-05-18 18:37:19 +02:00
6d4ccc5feb fix pointer variable not getting marked as used in some cases 2025-05-18 13:23:56 +02:00
5f3829d5cc partly fix weird errors for ptr indexed expressions 2025-05-17 22:56:41 +02:00
770ebdcd4a party fix weird errors for ptr indexed assignment 2025-05-17 20:54:02 +02:00
96f690e749 fix a ptr indexing error 2025-05-17 20:27:38 +02:00
eabdd3a8f3 fix the ptr.uword[index] assignment target error 2025-05-17 14:57:24 +02:00
50650b966b repeat countervars again in zeropage if possible, fix pointer arithmetic error 2025-05-17 14:18:56 +02:00
65e34d4989 stricter types for & operator (address-of), and fix invalid recursive subroutine flagging related to struct definition 2025-05-17 11:32:54 +02:00
05dad5ab5f cleanup 6502 pointer usage checks 2025-05-16 21:44:28 +02:00
1a69a2f1bc fix some ptr vs uword type checks 2025-05-16 20:31:15 +02:00
435faafaad fix split-word storage (lsb/msb) of arrays of pointers 2025-05-16 17:53:15 +02:00
686b32dc29 replace ^^str by ^^ubyte and allow returning ubyte/uword when pointer is expected 2025-05-16 17:53:15 +02:00
0e64a22910 tweak address-of types 2025-05-16 17:53:15 +02:00
4f0839f27e rewrite pointer[0] into @(pointer) if its ^^ubyte 2025-05-16 17:53:15 +02:00
bb1953267d Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-05-15 23:09:30 +02:00
acc630972a make keyboardhandler example restartable 2025-05-15 23:07:54 +02:00
6a33be3fd8 IR: allow returning boolean in Pc/Pv cpu status register (Pz and Pn are not yet possible) 2025-05-15 22:56:45 +02:00
cd8aae4681 allow @(..) to take a ^^ubyte address pointer, not only uwords. 2025-05-15 22:12:42 +02:00
11456496bd Merge branch 'master' into structs
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-15 21:35:31 +02:00
f5fc4e345c fix build error on case-insensitive filesystems 2025-05-15 21:11:34 +02:00
86eef7039f @(..) now also accepts pointer to ubyte address 2025-05-15 20:07:02 +02:00
f4b2264fcf fix struct type checks for subroutine call arguments 2025-05-14 23:33:55 +02:00
9b36ae2277 implement inplace boolean short-circuit operators on pointer dereferenced booleans 2025-05-14 21:29:50 +02:00
913ab03963 get rid of invalid ARRAY_STRUCT data type (arrays of struct instance are not yet supported) 2025-05-14 20:43:00 +02:00
38448e471c Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
#	examples/test.p8
2025-05-13 23:42:13 +02:00
67231af623 fix forloop codegen over non-split word arrays of length >= 64 elements 2025-05-13 23:32:26 +02:00
e31ef6f06f IR: fix temp register type in for x in array 2025-05-13 22:23:04 +02:00
09d188106a different temp var mechanism for certain array expression, remove old tmpvar mechanism. 2025-05-13 21:12:10 +02:00
d8e2116481 different temp var mechanism for for loops, and pokef() 2025-05-13 21:06:33 +02:00
435dfbb932 optimize: rewrite suitable when into on..goto 2025-05-13 01:12:58 +02:00
ba93966474 optimize codegen: shortcut redundant jumps in when statement 2025-05-13 00:35:22 +02:00
ea8d17cdb2 optimized the cx16 multi-irq dispatcher used in cx16.enable_irq_handlers() 2025-05-12 23:26:54 +02:00
082265fb25 todo 2025-05-12 00:24:57 +02:00
d138a7a567 add struct and pointers to IDEA syntax 2025-05-11 23:35:53 +02:00
ea27d732ab Merge branch 'refs/heads/master' into structs 2025-05-11 23:35:13 +02:00
9e557ce8ac add keyword 'on' to IDEA syntax 2025-05-11 23:32:54 +02:00
924e28e9b3 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt
#	compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/ast/walk/AstWalker.kt
#	compilerAst/src/prog8/ast/walk/IAstVisitor.kt
#	docs/source/todo.rst
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-11 23:23:06 +02:00
e5d9af75de remove double bra/jmp 2025-05-11 23:01:13 +02:00
31c1bf8bc5 added on..goto/call statement 2025-05-11 21:37:44 +02:00
37d4055036 translate newline '\n' to char code 13 in various encodings such as ISO (used to be 10)
This means that when printed, such newlines will now properly go to the next line in these encodings too (ISO variants, KATAKANA).
2025-05-11 19:45:24 +02:00
78b1076110 some more software links 2025-05-11 19:39:20 +02:00
0a3c748e41 fix code gen bug for certain goto array[idx] 2025-05-11 17:18:20 +02:00
ebf79ef9e2 release 11.3.2 2025-05-11 12:30:36 +02:00
60a73248cd todo 2025-05-11 03:16:48 +02:00
abbb7d7ba3 fix struct pointers in subroutine parameters and return values 2025-05-11 02:08:59 +02:00
59c378089e fix some struct type and symbol lookup errors 2025-05-11 00:52:35 +02:00
0b789b5f0b added most inplace operators for pointer deref 2025-05-10 20:58:01 +02:00
4382b96a9a tweaking pointer deref in IR 2025-05-10 19:52:06 +02:00
246e4f35a6 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	compiler/test/ast/TestConst.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-05-10 16:36:16 +02:00
99b9370178 fix various bugs around word-indexing combined with address-of: &buffer[2000] 2025-05-10 16:22:05 +02:00
506062c6b6 start implementing ptr deref augmented assigns 2025-05-09 23:05:27 +02:00
d634061cd9 Merge pull request #169 from Frosty-J/crc
Specify X16 CRC type
2025-05-08 23:47:17 +02:00
8353c689ca start making '&' (address-of) return a typed pointer, fixes some errors 2025-05-08 23:40:41 +02:00
d59d8ff1fe Specify X16 CRC type 2025-05-08 05:57:30 +01:00
e98e6f70ac Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-05-07 23:26:21 +02:00
53e442d509 fix regression in 11.3.1: allow bitwise operation between different types as long as they're the same size. 2025-05-07 23:06:45 +02:00
134352ed7c Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-07 22:32:41 +02:00
f7cbfdff06 skip this test for now 2025-05-07 21:14:52 +02:00
b28ee0819f revert behavior change of @dirty variables, instead document the initialization behavior correctly in the docs
they get zeroed at program startup (like other uninitialized BSS variables), just not on entry in the subroutine.
2025-05-07 21:07:12 +02:00
5de626aab8 support comparison operators on pointers 2025-05-06 22:26:27 +02:00
7aad5d486e Merge pull request #168 from adiee5/master
Update `nano` syntax highlighting
2025-05-06 18:17:58 +02:00
701f155951 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt
#	compiler/test/TestSymbolTable.kt
#	docs/source/todo.rst
#	examples/test.p8
#	intermediate/src/prog8/intermediate/IRFileReader.kt
#	intermediate/src/prog8/intermediate/IRFileWriter.kt
#	intermediate/src/prog8/intermediate/IRSymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTableMaker.kt
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-06 17:59:36 +02:00
8c324d7514 tweak error 2025-05-06 17:50:53 +02:00
522958e0e9 @dirty variables now actually end up in the uninitialized BSS_NOCLEAR section 2025-05-06 01:41:34 +02:00
97390db5f5 Update prog8.nanorc 2025-05-05 21:06:56 +02:00
af920d1427 pointer arithmetic for '-', fixed '+' 2025-05-05 21:06:49 +02:00
779ebc0537 pointer arithmetic for '+' 2025-05-05 18:09:19 +02:00
38949b82c3 type check tuning 2025-05-05 15:41:32 +02:00
d11386ef26 type check tuning 2025-05-04 23:23:21 +02:00
0e0377d1f0 IR/VM implemented struct allocations and initialization 2025-05-04 14:04:44 +02:00
55e0dbab27 preparing for statically allocating struct instances 2025-05-03 23:44:29 +02:00
4dc82f2c83 preparing for statically allocating struct instances 2025-05-03 19:00:27 +02:00
1ba5587404 allow syntax for declaring variables with struct instance type 2025-05-03 16:14:22 +02:00
835c4b6da3 allow multi-field declarations in structs, get rid of . -> ^^ rewrite 2025-05-03 12:32:29 +02:00
dbd955b61e tweak typecheck and better code for constant indexes in pointer derefs 2025-05-02 23:44:26 +02:00
d20e2fd88c structs will be a new major version of the compiler 2025-05-02 22:25:26 +02:00
e0dea89477 added support for ptr[x].field 2025-05-02 22:16:20 +02:00
ccc6b56e35 added link to prog8reu library 2025-05-02 19:38:59 +02:00
6fc2902895 fixing ptr traversal typecheck issues 2025-05-02 00:41:42 +02:00
c96e4b40d4 building syntax support for ptr[x].field
attempting to do this by making '.' an expression operator
2025-05-02 00:41:42 +02:00
37da3e2170 parser 2025-05-02 00:41:42 +02:00
2661d3c489 allow array syntax on pointers 2025-05-02 00:41:42 +02:00
b89bbb9281 allow pointers in subroutines params and return values 2025-05-02 00:41:42 +02:00
696bf636ed better parsing of directive names results in better error messages when an invalid one is found 2025-05-02 00:41:42 +02:00
40952a788a PtSub: params and returns now as children (in PtSignature node) for easier Ast walking
PtPointerDeref: same but for its start identifier
2025-05-02 00:41:42 +02:00
0162e7a0c1 fix the scoping problems on subtypes 2025-05-02 00:41:42 +02:00
6ce099f176 IR: fix ptr type checks and struct field assignment errors 2025-05-02 00:41:42 +02:00
476a4bac8e IR: LOADI allows r1 and r2 to be the same for pointer chain dereference optimalization 2025-05-02 00:41:42 +02:00
63a410a6df implicit cast to bool for numeric or pointers as condition arguments (to if, while, until) 2025-05-02 00:41:42 +02:00
cca27faa3b fix pointer value assignment (not dereferencing the actual pointer) 2025-05-02 00:41:42 +02:00
803e6bd81a fix uword vs pointer type errors and casts 2025-05-02 00:41:42 +02:00
88269628a2 had to turn ^type syntax into ^^type to avoid confusion with the eor operator once again 2025-05-02 00:41:42 +02:00
b920d553a0 make address-of dereference work 2025-05-02 00:41:42 +02:00
5e2d0d0dfc fix param order of AssignTarget 2025-05-02 00:41:42 +02:00
2ae3bd68eb more pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
9c183f27eb pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
8046023e82 pointer dereferencing for simple types (read and write) 2025-05-02 00:41:42 +02:00
e328520588 initial struct and typed pointer support 2025-05-02 00:41:40 +02:00
7eb079050c fix strings.hash() on 6502 CPU's. NOTE: now takes string pointer in AY instead of R0 on all platforms 2025-05-01 22:47:43 +02:00
2fdd5543b2 release 11.3.1 2025-04-30 22:40:54 +02:00
d04164c0a6 fix const evaluation of bitwise logical expressions (&, |, ^, <<, >>) of signed operands 2025-04-30 22:27:31 +02:00
b047731f82 removed some redundant arguments 2025-04-25 23:27:06 +02:00
4d91f92a2e clearer error message 2025-04-25 19:38:45 +02:00
98505d27b1 IR: fix invalid removal of label chunks and include asmsubs when looking for labels 2025-04-24 23:30:51 +02:00
cd63a58ad9 IR: get rid of problematic PREPARECALL pseudo instruction 2025-04-24 22:32:40 +02:00
170f8dd092 IR: new CALLFAR instructions to support callfar into another bank 2025-04-24 21:17:48 +02:00
619dcb6a84 fix error in IR register typing 2025-04-24 20:50:19 +02:00
99ae8ea52e code cleanup 2025-04-24 18:51:03 +02:00
dc031c30eb add -version option to just print the compiler version and then exit 2025-04-24 18:13:35 +02:00
1e702439b7 avoid JDK 21+/Kotlin method conflict of removeLast()/removeFirst()
background: see https://www.reddit.com/r/androiddev/comments/1gspjrs/dont_use_kotlins_removefirst_and_removelast_when/
https://youtrack.jetbrains.com/issue/KT-71375/Prevent-Kotlins-removeFirst-and-removeLast-from-causing-crashes-on-Android-14-and-below-after-upgrading-to-Android-API-Level-35

it's about Android but the problem also occurs on desktop JDKs for example when running a Java21 compiled prog8 with Java17
2025-04-24 18:11:42 +02:00
8debc42381 slightly stricter assignment type compatibility checking 2025-04-24 15:05:35 +02:00
532d719089 more optimal math.randrange() routines 2025-04-24 04:04:14 +02:00
b40860aca4 get rid of RTS after JMP 2025-04-23 17:08:16 +02:00
2cbe6b5f7f info message when more optimal goto array[idx] is possible 2025-04-23 16:59:18 +02:00
d2cc7ccdfa remove redundant variable=0 initializations (BSS clear takes care of them) 2025-04-23 14:45:38 +02:00
2cb183c6d8 fix regression for goto array[idx] on 6502 cpu which doesn't have jmp (ptr,x) 2025-04-23 02:56:10 +02:00
188 changed files with 10928 additions and 4331 deletions

3
.gitignore vendored
View File

@ -7,10 +7,13 @@ build/
dist/
output/
out/
out-new/
out-old/
.*cache/
*.directory
*.prg
*.bin
*.p8ir
*.labels.txt
*.vm.txt
*.vice-mon-list

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.21/kotlin-stdlib-jdk8-2.1.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.21/kotlin-stdlib-jdk7-2.1.21.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.21/kotlin-stdlib-jdk8-2.1.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.21/kotlin-stdlib-jdk7-2.1.21-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.21/kotlin-stdlib-jdk8-2.1.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.21/kotlin-stdlib-jdk7-2.1.21-sources.jar!/" />
</SOURCES>
</library>
</component>

1
.idea/misc.xml generated
View File

@ -12,6 +12,7 @@
<option name="pkg" value="" />
<option name="language" value="Java" />
<option name="generateListener" value="false" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>

View File

@ -71,6 +71,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``

View File

@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
plugins {
kotlin("jvm") version "2.1.20"
kotlin("jvm") version "2.1.21"
}
allprojects {

View File

@ -101,7 +101,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *BaseDataType.entries.toTypedArray())),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),

View File

@ -13,6 +13,9 @@ enum class BaseDataType {
STR, // pass by reference
ARRAY, // pass by reference, subtype is the element type
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
UNDEFINED;
@ -26,6 +29,7 @@ enum class BaseDataType {
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
@ -34,7 +38,8 @@ enum class BaseDataType {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
@ -50,84 +55,163 @@ val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, Bas
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?) {
interface ISubType {
val scopedNameString: String
fun memsize(sizer: IMemSizer): Int
}
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) {
init {
if(base.isArray) {
require(sub != null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
when {
base.isPointerArray -> {
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
else -> {
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
"sub and subtype can't both be set"
}
}
}
else if(base==BaseDataType.STR)
require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
else
require(sub == null) { "only string and array base types can have a subtype"}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub
return base == other.base && sub == other.sub && subType == other.subType
}
override fun hashCode(): Int = Objects.hash(base, sub)
override fun hashCode(): Int = Objects.hash(base, sub, subType)
fun setActualSubType(actualSubType: ISubType) {
subType = actualSubType
subTypeFromAntlr = null
}
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null)
val BYTE = DataType(BaseDataType.BYTE, null)
val UWORD = DataType(BaseDataType.UWORD, null)
val WORD = DataType(BaseDataType.WORD, null)
val LONG = DataType(BaseDataType.LONG, null)
val FLOAT = DataType(BaseDataType.FLOAT, null)
val BOOL = DataType(BaseDataType.BOOL, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null)
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null)
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType) = simpletypes.getValue(dt)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
DataType(BaseDataType.ARRAY, actualElementDt)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid element dt $elementDt")
throw NoSuchElementException("invalid basic element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType {
if(dt.isBasic)
return DataType(BaseDataType.POINTER, dt.base, null)
else
return DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
}
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType, null)
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
}
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
if(base.isArray || base==BaseDataType.STR)
forDt(sub!!)
else
throw IllegalArgumentException("not an array")
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
if (isArray) {
if (msb || isSplitWordArray)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
}
if (subType != null)
return pointer(this)
return UWORD
}
}
fun dereference(): DataType {
require(isPointer || isUnsignedWord)
return when {
isUnsignedWord -> forDt(BaseDataType.UBYTE)
sub!=null -> forDt(sub)
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
else -> throw IllegalArgumentException("cannot dereference this pointer type")
}
}
override fun toString(): String = when(base) {
BaseDataType.ARRAY -> {
@ -148,6 +232,15 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
}
BaseDataType.STRUCT_INSTANCE -> {
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
}
else -> base.name.lowercase()
}
@ -160,6 +253,30 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "str"
BaseDataType.POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}"
subType!=null -> "^^${subType!!.scopedNameString}"
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
else -> "?????"
}
}
BaseDataType.STRUCT_INSTANCE -> {
when {
sub!=null -> sub.name.lowercase()
subType!=null -> subType!!.scopedNameString
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
else -> "?????"
}
}
BaseDataType.ARRAY_POINTER -> {
when {
sub!=null -> "^^${sub.name.lowercase()}["
subType!=null -> "^^${subType!!.scopedNameString}["
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
else -> "????? ["
}
}
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
@ -187,18 +304,36 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
targetType.isPointer -> this.isUnsignedWord || this==targetType
else -> false
}
}
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
BaseDataType.ARRAY_POINTER -> TODO("check assignability of array of pointers")
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
memsizer.memorySize(sub)
} else if(subType!=null) {
subType!!.memsize(memsizer)
} else {
memsizer.memorySize(base)
}
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
val isUndefined = base == BaseDataType.UNDEFINED
val isByte = base.isByte
val isUnsignedByte = base == BaseDataType.UBYTE
@ -214,6 +349,9 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isPointer = base.isPointer
val isStructInstance = base.isStructInstance
val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub == BaseDataType.UBYTE
@ -280,7 +418,7 @@ enum class RegisterOrPair {
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, null -> ""
else -> throw kotlin.IllegalArgumentException("invalid register param type")
else -> throw IllegalArgumentException("invalid register param type")
}
return listOf("cx16", name.lowercase()+suffix)
}

View File

@ -14,5 +14,5 @@ interface IErrorReporter {
fun noErrorForLine(position: Position): Boolean
fun print_single_error(errormessage: String)
fun printSingleError(errormessage: String)
}

View File

@ -6,7 +6,10 @@ import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
@ -28,10 +31,10 @@ class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by N
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@ -7,7 +7,10 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@ -37,7 +37,7 @@ class ConfigFileTarget(
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(8) {
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {

View File

@ -6,7 +6,10 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@ -7,7 +7,9 @@ import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
@ -25,7 +27,9 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isLong -> 4 * (numElements ?: 1)
dt.isPointer -> 2 // pointer is just a uword
dt.isStructInstance -> dt.subType!!.memsize(this)
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}

View File

@ -6,7 +6,10 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@ -7,7 +7,10 @@ import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
@ -88,31 +91,6 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
}

View File

@ -197,6 +197,7 @@ object AtasciiEncoding {
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\r' -> 0x9bu
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly

View File

@ -285,6 +285,7 @@ object C64osEncoding {
val screencode = encodingC64os[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 13u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@ -5,19 +5,19 @@ import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
object Encoder: IStringEncoding {
class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.C64OS -> C64osEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@ -30,12 +30,12 @@ object Encoder: IStringEncoding {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.C64OS -> C64osEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}

View File

@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
@ -112,9 +113,14 @@ object KatakanaEncoding {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@ -21,7 +21,7 @@ object PetsciiEncoding {
'\ufffe', // 0x07 -> UNDEFINED
'\uf118', // 0x08 -> DISABLE CHARACTER SET SWITCHING (CUS)
'\uf119', // 0x09 -> ENABLE CHARACTER SET SWITCHING (CUS)
'\ufffe', // 0x0A -> UNDEFINED
'\n', // 0x0A -> LINE FEED (RETURN)
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
@ -1117,6 +1117,8 @@ object PetsciiEncoding {
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 141u
'\r' -> 141u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@ -16,12 +16,6 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
if (options.floats) {
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@ -19,7 +19,7 @@ class ConfigurableZeropage(
init {
if (options.floats) {
TODO("floats")
TODO("floats in configurable target zp")
}
if(SCRATCH_REG!=SCRATCH_B1+1u)
@ -30,7 +30,7 @@ class ConfigurableZeropage(
ZeropageType.FULL -> fullsafe.forEach { free.addAll(it) }
ZeropageType.BASICSAFE -> basicsafe.forEach { free.addAll(it) }
ZeropageType.KERNALSAFE -> kernalsafe.forEach { free.addAll(it) }
ZeropageType.FLOATSAFE -> TODO("floatsafe")
ZeropageType.FLOATSAFE -> TODO("floatsafe in configurable target zp")
}
val distinctFree = free.distinct()

View File

@ -16,10 +16,6 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@ -37,7 +37,10 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
is PtBlock -> node.name = "p8b_${node.name}"
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> {
node.name = "p8v_${node.name}"
}
is PtStructDecl -> { /* do nothing */ }
}
}
@ -50,10 +53,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
}
}
is PtSub -> {
prefixNamedNode(node)
node.parameters.forEach { prefixNamedNode(it) }
}
is PtSub -> prefixNamedNode(node)
is PtFunctionCall -> {
val stNode = st.lookup(node.name)!!
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
@ -175,10 +175,10 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
newValue.add(elt)
else {
val newAddr = PtAddressOf(elt.position)
newAddr.children.add(elt.identifier.prefix(newAddr, st))
if(elt.arrayIndexExpr!=null)
newAddr.children.add(elt.arrayIndexExpr!!)
val newAddr = PtAddressOf(elt.type, elt.position)
newAddr.add(elt.identifier!!.prefix(newAddr, st))
if (elt.arrayIndexExpr != null)
newAddr.add(elt.arrayIndexExpr!!)
newAddr.parent = arrayValue
newValue.add(newAddr)
}
@ -186,7 +186,7 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
val result = PtVariable(name, type, zeropage, align, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
result.parent = parent
result
}
@ -227,6 +227,7 @@ private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
StNodeType.CONSTANT -> 'c'
StNodeType.BUILTINFUNC -> 's'
StNodeType.MEMORYSLAB -> 'v'
StNodeType.STRUCTINSTANCE -> 'i'
else -> '?'
}
val newName = prefixScopedName(name, prefixType)
@ -316,9 +317,9 @@ class AsmGen6502Internal (
if(symbolTable.allVariables.isNotEmpty()) {
println("Static variables (not in ZeroPage):")
symbolTable.allVariables
.filterNot { allocator.isZpVar(it.scopedName) }
.sortedBy { it.scopedName }.forEach {
println(" ${it.dt}\t${it.scopedName}\t")
.filterNot { allocator.isZpVar(it.scopedNameString) }
.sortedBy { it.scopedNameString }.forEach {
println(" ${it.dt}\t${it.scopedNameString}\t")
}
}
if(allocator.globalFloatConsts.isNotEmpty()) {
@ -330,9 +331,9 @@ class AsmGen6502Internal (
if(symbolTable.allMemMappedVariables.isNotEmpty()) {
println("Memory mapped:")
symbolTable.allMemMappedVariables
.sortedWith( compareBy( {it.address}, {it.scopedName} ))
.sortedWith( compareBy( {it.address}, {it.scopedNameString} ))
.forEach { mvar ->
println(" $${mvar.address.toString(16).padStart(4, '0')}\t${mvar.dt}\t${mvar.scopedName}")
println(" $${mvar.address.toString(16).padStart(4, '0')}\t${mvar.dt}\t${mvar.scopedNameString}")
}
}
if(symbolTable.allMemorySlabs.isNotEmpty()) {
@ -420,7 +421,7 @@ class AsmGen6502Internal (
}
fun asmVariableName(st: StNode, scope: IPtSubroutine?): String {
val name = asmVariableName(st.scopedName)
val name = asmVariableName(st.scopedNameString)
if(scope==null)
return name
// remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
@ -432,23 +433,6 @@ class AsmGen6502Internal (
}
internal val tempVarsCounters = mutableMapOf(
BaseDataType.BOOL to 0,
BaseDataType.BYTE to 0,
BaseDataType.UBYTE to 0,
BaseDataType.WORD to 0,
BaseDataType.UWORD to 0,
BaseDataType.LONG to 0,
BaseDataType.FLOAT to 0
)
internal fun buildTempVarName(dt: BaseDataType, counter: Int): String = "prog8_tmpvar_${dt.toString().lowercase()}_$counter"
internal fun getTempVarName(dt: BaseDataType): String {
tempVarsCounters[dt] = tempVarsCounters.getValue(dt)+1
return buildTempVarName(dt, tempVarsCounters.getValue(dt))
}
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
// returns the source name of the zero page pointervar if it's already in the ZP,
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
@ -638,7 +622,7 @@ class AsmGen6502Internal (
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtNodeGroup -> stmt.children.forEach { translate(it) }
is PtJmpTable -> translate(stmt)
is PtNop -> {}
is PtNop, is PtStructDecl, is PtSubSignature -> {}
else -> throw AssemblyError("missing asm translation for $stmt")
}
}
@ -656,16 +640,16 @@ class AsmGen6502Internal (
}
if(expr.splitWords) {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register))
return
}
when {
expr.type.isByteOrBool -> {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register))
}
expr.type.isWord -> {
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out(" asl a")
when (register) {
CpuRegister.A -> {}
@ -674,8 +658,9 @@ class AsmGen6502Internal (
}
}
expr.type.isFloat -> {
require(options.compTarget.FLOAT_MEM_SIZE == 5) {"invalid float size ${expr.position}"}
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
if(options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${expr.position}")
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out("""
sta P8ZP_SCRATCH_REG
asl a
@ -750,7 +735,7 @@ class AsmGen6502Internal (
TargetStorageKind.REGISTER -> {
val zero = PtNumber(BaseDataType.UBYTE, 0.0, value.position)
zero.parent = value
assignExpressionToRegister(zero, target.register!!, false)
assignExpressionToRegister(zero, target.register!!)
return
}
else -> { }
@ -854,7 +839,7 @@ class AsmGen6502Internal (
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, true, stmt)
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$loopcount
@ -874,7 +859,7 @@ $repeatLabel""")
// note: A/Y must have been loaded with the number of iterations!
// the iny + double dec is microoptimization of the 16 bit loop
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, true, stmt)
out("""
cmp #0
beq +
@ -897,13 +882,13 @@ $repeatLabel""")
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
@ -915,13 +900,13 @@ $repeatLabel""")
val repeatLabel = makeLabel("repeat")
out(" cpy #0")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
@ -930,43 +915,9 @@ $repeatLabel""")
out(endLabel)
}
private fun createRepeatCounterVar(dt: BaseDataType, preferZeropage: Boolean, stmt: PtRepeatLoop): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("counter") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null)
return existingVar.second
}
}
val counterVar = makeLabel("counter")
when(dt) {
BaseDataType.UBYTE, BaseDataType.UWORD -> {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
return counterVar
}
else -> throw AssemblyError("invalidt dt")
}
}
private fun translate(stmt: PtWhen) {
val endLabel = makeLabel("when_end")
val choiceBlocks = mutableListOf<Pair<String, PtNodeGroup>>()
val choiceBlocks = mutableListOf<Pair<String, PtWhenChoice>>()
val conditionDt = stmt.value.type
if(conditionDt.isByte)
assignExpressionToRegister(stmt.value, RegisterOrPair.A)
@ -977,13 +928,20 @@ $repeatLabel""")
val choice = choiceNode as PtWhenChoice
var choiceLabel = makeLabel("choice")
if(choice.isElse) {
require(choice.parent.children.last() === choice)
translate(choice.statements)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choiceLabel = endLabel
} else {
choiceBlocks.add(choiceLabel to choice.statements)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
if(onlyJumpLabel==null) {
choiceBlocks.add(choiceLabel to choice)
} else {
choiceLabel = onlyJumpLabel
}
}
for (cv in choice.values.children) {
val value = (cv as PtNumber).number.toInt()
@ -1000,11 +958,14 @@ $repeatLabel""")
}
}
}
jmp(endLabel)
if(choiceBlocks.isNotEmpty())
jmp(endLabel)
for(choiceBlock in choiceBlocks.withIndex()) {
out(choiceBlock.value.first)
translate(choiceBlock.value.second)
if (choiceBlock.index < choiceBlocks.size - 1)
translate(choiceBlock.value.second.statements)
if (choiceBlock.index < choiceBlocks.size - 1 && !choiceBlock.value.second.isOnlyGotoOrReturn())
jmp(endLabel)
}
out(endLabel)
@ -1033,6 +994,7 @@ $repeatLabel""")
val target = getJumpTarget(jump)
require(!target.needsExpressionEvaluation)
if(target.indirect) {
require(!target.indexedX)
val complementedInstruction = branchInstruction(stmt.condition, true)
out("""
$complementedInstruction +
@ -1084,12 +1046,29 @@ $repeatLabel""")
else {
if(evaluateAddressExpression) {
val arrayIdx = jump.target as? PtArrayIndexer
if(arrayIdx!=null && !arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// more optimal assembly can be generated using JMP address,X
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
if (arrayIdx!=null) {
val arrayVariable = arrayIdx.variable
if(arrayVariable==null)
TODO("support for ptr indexing ${arrayIdx.position}")
if (isTargetCpu(CpuType.CPU65C02)) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayVariable), true, true, false)
} else {
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayVariable.name)!!
if(variable is StStaticVariable && variable.length!!<=128u)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable
assignExpressionToVariable(jump.target, "P8ZP_SCRATCH_W1", DataType.UWORD)
@ -1101,17 +1080,18 @@ $repeatLabel""")
}
private fun translate(ret: PtReturn) {
val returnvalue = ret.children.singleOrNull()
val returnvalue = ret.children.singleOrNull() as? PtExpression
val sub = ret.definingSub()!!
val returnRegs = sub.returnsWhatWhere()
if(returnvalue!=null) {
if (sub.returns.single().isNumericOrBool) {
assignExpressionToRegister(returnvalue as PtExpression, returnRegs.single().first.registerOrPair!!)
if (sub.signature.returns.single().isNumericOrBool) {
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!)
}
else {
// all else take its address and assign that also to AY register pair
val addrofValue = PtAddressOf(returnvalue.position)
val addrOfDt = returnvalue.type.typeForAddressOf(false)
val addrofValue = PtAddressOf(addrOfDt, returnvalue.position)
addrofValue.add(returnvalue as PtIdentifier)
addrofValue.parent = ret.parent
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnRegs.single().first.registerOrPair!!, false)
@ -1279,7 +1259,7 @@ $repeatLabel""")
}
if(addressExpr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr, false)
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr)
if (ptrAndIndex == null) return false
if(write) {
@ -1291,8 +1271,10 @@ $repeatLabel""")
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
} else if(addrOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addrOf.position}")
} else {
out(" sta ${asmSymbolName(addrOf.identifier)}+${constOffset}")
out(" sta ${asmSymbolName(addrOf.identifier!!)}+${constOffset}")
return true
}
}
@ -1331,8 +1313,10 @@ $repeatLabel""")
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
} else if(addrOf.dereference!=null) {
TODO("read &dereference")
} else {
out(" lda ${asmSymbolName(addrOf.identifier)}+${constOffset}")
out(" lda ${asmSymbolName(addrOf.identifier!!)}+${constOffset}")
return true
}
}
@ -1381,8 +1365,10 @@ $repeatLabel""")
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
} else if(addrOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addrOf.position}")
} else {
out(" sta ${asmSymbolName(addrOf.identifier)}-${constOffset}")
out(" sta ${asmSymbolName(addrOf.identifier!!)}-${constOffset}")
return true
}
}
@ -1412,8 +1398,10 @@ $repeatLabel""")
if(addrOf!=null && constOffset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
} else if(addrOf.dereference!=null) {
TODO("read &dereference")
} else {
out(" lda ${asmSymbolName(addrOf.identifier)}-${constOffset}")
out(" lda ${asmSymbolName(addrOf.identifier!!)}-${constOffset}")
return true
}
}
@ -1444,7 +1432,15 @@ $repeatLabel""")
val node = stScope.astNode
if(node is PtSubroutineParameter)
return node
return node!!.definingSub()?.parameters?.singleOrNull { it.name===name }
val params = node!!.definingSub()?.signature?.children
if(params!=null) {
for(param in params) {
param as PtSubroutineParameter
if(param.name==name)
return param
}
}
return null
}
internal fun assignByteOperandsToAAndVar(left: PtExpression, right: PtExpression, rightVarName: String) {
@ -1549,6 +1545,51 @@ $repeatLabel""")
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
}
internal fun createTempVarReused(dt: BaseDataType, preferZeropage: Boolean, stmt: PtNode): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop || parent is PtForLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop || parent is PtForLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("tempv") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null) {
// println("reuse temp counter var: $dt ${existingVar.second} @${stmt.position}")
return existingVar.second
}
}
}
val counterVar = makeLabel("tempv")
// println("new temp counter var: $dt $counterVar @${stmt.position}")
when {
dt.isIntegerOrBool -> {
if(preferZeropage) {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
} else {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally
}
return counterVar
}
dt == BaseDataType.FLOAT -> {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally, floats never on zeropage
return counterVar
}
else -> throw AssemblyError("invalid dt")
}
}
internal fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""
@ -1569,7 +1610,7 @@ $repeatLabel""")
val compare = if(useSbc) "sec | sbc" else "cmp"
fun cmpViaScratch() {
if(assignmentAsmGen.directIntoY(value)) {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
assignExpressionToRegister(value, RegisterOrPair.Y)
out(" sty P8ZP_SCRATCH_REG")
} else {
out(" pha")
@ -1585,7 +1626,9 @@ $repeatLabel""")
if(constIndex!=null) {
val offset = program.memsizer.memorySize(value.type, constIndex)
if(offset<256) {
return out(" ldy #$offset | $compare ${asmVariableName(value.variable)},y")
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
return out(" ldy #$offset | $compare ${asmVariableName(value.variable!!)},y")
}
}
cmpViaScratch()
@ -1628,7 +1671,7 @@ $repeatLabel""")
}
internal fun assignConditionValueToRegisterAndTest(condition: PtExpression) {
assignExpressionToRegister(condition, RegisterOrPair.A, false)
assignExpressionToRegister(condition, RegisterOrPair.A)
when(condition) {
is PtNumber,
is PtBool,

View File

@ -508,9 +508,11 @@ private fun optimizeIncDec(linesByFour: Sequence<List<IndexedValue<String>>>): L
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
// jmp Sub + rts -> jmp Sub
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp (bra bra / jmp jmp are not removed because this is likely a jump table!)
// and some other optimizations.
val mods = mutableListOf<Modification>()
@ -520,7 +522,10 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
val third = lines[2].value
if(!haslabel(second)) {
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
if ((" jmp" in first || "\tjmp" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[1].index, true, null)
}
else if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
if("floats.pushFAC" !in first && "floats.popFAC" !in first) { // these 2 routines depend on being called with JSR!!
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
@ -563,6 +568,15 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
}
}
// only remove bra followed by jmp or jmp followed by bra
// bra bra or jmp jmp is likely part of a jump table, which should keep all entries!
if((" bra" in first || "\tbra" in first) && (" jmp" in second || "\tjmp" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
if((" jmp" in first || "\tjmp" in first) && (" bra" in second || "\tbra" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
}
/*

View File

@ -44,6 +44,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"structalloc" -> funcStructAlloc(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
@ -53,7 +54,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val memread = PtMemoryByte(fcall.position)
memread.add(fcall.args[0])
memread.parent = fcall
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A)
asmgen.out(" pha")
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
asmgen.assignExpressionTo(fcall.args[1], memtarget)
@ -380,8 +381,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(fcall.position)
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
@ -390,6 +392,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcStructAlloc(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult)
throw AssemblyError("should not discard result of struct allocation at $fcall")
if(fcall.args.isEmpty())
TODO("struct alloc in BSS")
else
TODO("static struct alloc with values")
}
private fun funcSqrt(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
when(fcall.args[0].type.base) {
@ -415,8 +427,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
@ -438,8 +453,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
else
@ -465,7 +482,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" ror ${varname},x")
}
is PtMemoryByte -> {
@ -498,7 +518,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
else
@ -521,8 +543,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
@ -545,7 +569,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (what) {
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
else
@ -571,7 +597,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" rol ${varname},x")
}
is PtMemoryByte -> {
@ -604,7 +632,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
else
@ -634,6 +664,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
if(arrayVariable==null)
TODO("support for ptr indexing ${indexer.position}")
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.arrayFor(BaseDataType.UBYTE, false), arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
@ -676,7 +708,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(fcall.args[1].asConstInteger() == 0) {
assignAsmGen.assignConstantByte(target, 0)
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
}
@ -739,7 +771,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
@ -815,7 +847,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr floats.MOVFM")
if(resultRegister!=null) {
assignAsmGen.assignFAC1float(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, null, null, null, resultRegister, null))
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position))
}
}
@ -1112,7 +1144,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(arg is PtArrayIndexer && resultRegister in arrayOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the msb byte out of the word array
if(arg.splitWords) {
val arrayVar = asmgen.asmVariableName(arg.variable)+"_msb"
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = asmgen.asmVariableName(arg.variable!!)+"_msb"
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@ -1129,7 +1163,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
val arrayVar = asmgen.asmVariableName(arg.variable)
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = asmgen.asmVariableName(arg.variable!!)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@ -1211,7 +1247,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
if(arg is PtArrayIndexer && resultRegister in arrayOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the lsb byte out of the word array
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
if(arg.variable==null)
TODO("support for ptr indexing ${arg.position}")
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable!!)+"_lsb" else asmgen.asmVariableName(arg.variable!!)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
@ -1276,7 +1315,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
fun getSourceForFloat(value: PtExpression): AsmAssignSource {
return when (value) {
is PtIdentifier -> {
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@ -1290,7 +1329,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), value.position)
addr.add(variable)
addr.parent = call
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT)
@ -1310,7 +1349,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@ -1328,7 +1367,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false),value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)

View File

@ -89,8 +89,8 @@ internal class ForLoopsAsmGen(
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
asmgen.out(" sta $toValueVar")
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
@ -136,7 +136,7 @@ internal class ForLoopsAsmGen(
// use self-modifying code to store the loop end comparison value
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
@ -186,7 +186,7 @@ $modifiedLabel cmp #0 ; modified
val stepsize = range.step.asConstInteger()!!
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
@ -296,7 +296,7 @@ $modifiedLabel cmp #0 ; modified
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out(" sta $toValueVar")
@ -420,6 +420,7 @@ $endLabel""")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable; there is self-modifying code below
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@ -442,7 +443,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@ -500,15 +500,15 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
val numElements: UInt = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
else -> 0u
}
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
@ -539,14 +539,17 @@ $endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
@ -562,7 +565,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
@ -576,7 +579,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@ -586,7 +592,7 @@ $loopLabel sty $indexVar
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
@ -602,7 +608,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
@ -616,8 +622,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val length = numElements * 2
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@ -627,16 +635,16 @@ $loopLabel sty $indexVar
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127) {
if(numElements<=127u) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
// array size is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
@ -645,7 +653,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(length>=16) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(

View File

@ -36,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
return
}
@ -139,15 +139,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
else if(sub is PtSub) {
if(optimizeIntArgsViaCpuRegisters(sub.parameters)) {
val parameters = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(optimizeIntArgsViaCpuRegisters(parameters)) {
// Note that if the args fit into cpu registers, we don't concern ourselves here
// if they should be put into regular subroutine parameter variables, or the R0-R15 register variables.
// That is now up to the subroutine itself.
useCpuRegistersForArgs(call.args, sub)
} else {
// arguments via variables
val paramValues = sub.parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { it.first.register == null }
val paramValues = parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) }
if (normalParams.isNotEmpty()) {
for (p in normalParams)
argumentViaVariable(sub, p.first, p.second)
@ -166,7 +167,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun useCpuRegistersForArgs(args: List<PtExpression>, sub: PtSub) {
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
when(params.size) {
1 -> {
val register = if (params[0].type.isByteOrBool) RegisterOrPair.A else RegisterOrPair.AY
@ -234,9 +235,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val param = sub.parameters[it]
val arg = call.args[it]
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
if(!registersUsed.any{r -> r.statusflag!=null || r.registerOrPair in CpuRegisters})
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
else if(registersUsed.any {it.statusflag!=null}) {
else if(registersUsed.any { r-> r.statusflag!=null }) {
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
@ -339,7 +340,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
val addr = PtAddressOf(value.type.typeForAddressOf(false),Position.DUMMY)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)

View File

@ -38,7 +38,6 @@ internal class IfElseAsmGen(private val program: PtProgram,
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
return
}
val rightDt = compareCond.right.type
@ -87,6 +86,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
@ -94,6 +94,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
@ -107,6 +108,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
@ -114,6 +116,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
@ -146,6 +149,93 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodiesSignedByte(elseConditional: String, value: PtExpression, stmt: PtIfElse) {
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
val afterIfLabel = asmgen.makeLabel("afterif")
asmgen.cmpAwithByteValue(value, true)
if(stmt.hasElse()) {
// if and else blocks
val elseLabel = asmgen.makeLabel("else")
branchElse(elseLabel)
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
// no else block
branchElse(afterIfLabel)
asmgen.translate(stmt.ifScope)
}
asmgen.out(afterIfLabel)
}
private fun translateJumpElseBodiesSignedByte(elseConditional: String, value: PtExpression, jump: PtJump, elseBlock: PtNodeGroup) {
fun branchTarget(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
var target = asmgen.getJumpTarget(jump, false)
asmgen.cmpAwithByteValue(value, true)
if(target.indirect) {
branchElse("+")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
branchTarget(target.asmLabel)
}
asmgen.translate(elseBlock)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@ -154,7 +244,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" $elseBranchInstr $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -171,10 +261,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" $falseBranch +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
asmgen.out("""
jmp (${target.asmLabel})
+""")
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
asmgen.out(" $branchInstr ${target.asmLabel}")
@ -210,38 +299,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", stmt)
}
"<" -> translateByteLess(stmt, signed, jumpAfterIf)
"<=" -> {
// X<=Y -> Y>=X (reverse of >=)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.left, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
"<=" -> translateByteLessEqual(stmt, signed, jumpAfterIf)
">" -> translateByteGreater(stmt, signed, jumpAfterIf)
">=" -> {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
">=" -> translateByteGreaterEqual(stmt, signed, jumpAfterIf)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
@ -286,7 +346,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" bmi + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -306,7 +367,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" bmi $elseLabel | beq $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -353,7 +414,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(target.indirect) {
asmgen.out(" bmi + | bne ++")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -375,7 +437,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
bpl $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -402,13 +464,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun translateByteLess(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcc", "bcs", jumpAfterIf, stmt.elseScope)
else
@ -416,25 +478,43 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteLessEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
// X<=Y -> Y>=X (reverse of >=)
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.left, stmt)
} else {
asmgen.cmpAwithByteValue(condition.left, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateByteGreater(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
asmgen.cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.left, stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A)
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null) {
var target = asmgen.getJumpTarget(jumpAfterIf, false)
if(target.indirect) {
asmgen.out(" bcc + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf, true)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -453,7 +533,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val elseLabel = asmgen.makeLabel("else")
asmgen.out(" bcc $elseLabel | beq $elseLabel")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -466,6 +546,23 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteGreaterEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@ -535,7 +632,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
eor #128
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -562,7 +660,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
eor #128
+ bmi $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -590,7 +688,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
cmp $valueLsb
bcs +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
_jump jmp (${target.asmLabel})
+""")
@ -615,7 +714,7 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcs $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -667,7 +766,8 @@ _jump jmp (${target.asmLabel})
eor #128
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -694,7 +794,7 @@ _jump jmp (${target.asmLabel})
eor #128
+ bmi $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -721,7 +821,8 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcc +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -744,7 +845,7 @@ _jump jmp (${target.asmLabel})
sbc $valueMsb
bcc $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -784,7 +885,9 @@ _jump jmp (${target.asmLabel})
private fun loadAndCmp0MSB(value: PtExpression) {
when(value) {
is PtArrayIndexer -> {
val varname = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varname = asmgen.asmVariableName(value.variable!!)
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords)
asmgen.out(" lda ${varname}_msb,y")
@ -800,8 +903,8 @@ _jump jmp (${target.asmLabel})
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(value.identifier)
if(value.identifier.type.isSplitWordArray) {
var varname = asmgen.asmVariableName(value.identifier!!)
if(value.identifier!!.type.isSplitWordArray) {
varname += if(value.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
@ -829,7 +932,8 @@ _jump jmp (${target.asmLabel})
lda $valueLsb
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -857,7 +961,7 @@ _jump jmp (${target.asmLabel})
bne $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -878,8 +982,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@ -908,7 +1014,8 @@ _jump jmp (${target.asmLabel})
cmp #0
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -936,7 +1043,7 @@ _jump jmp (${target.asmLabel})
bne $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -973,7 +1080,8 @@ _jump jmp (${target.asmLabel})
lda $valueLsb
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -1001,7 +1109,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1023,8 +1131,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@ -1053,7 +1163,8 @@ _jump jmp (${target.asmLabel})
cmp #0
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -1081,7 +1192,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1136,7 +1247,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if(constIndex!=null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "bne", "beq")
}
@ -1157,7 +1270,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if (constIndex != null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "beq", "bne")
}
@ -1196,7 +1311,8 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -1221,7 +1337,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1248,7 +1364,8 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -1273,7 +1390,7 @@ _jump jmp (${target.asmLabel})
cpy $valueMsb
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1302,7 +1419,8 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -1331,7 +1449,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1361,7 +1479,8 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -1390,7 +1509,7 @@ _jump jmp (${target.asmLabel})
cmp ${right.name}+1
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1423,7 +1542,8 @@ _jump jmp (${target.asmLabel})
cmp #>$value
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@ -1452,7 +1572,7 @@ _jump jmp (${target.asmLabel})
beq $elseLabel
+""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1481,7 +1601,8 @@ _jump jmp (${target.asmLabel})
cmp #>$value
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump, true)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@ -1510,7 +1631,7 @@ _jump jmp (${target.asmLabel})
cmp #>$value
bne $elseLabel""")
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel, false)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
@ -1532,8 +1653,10 @@ _jump jmp (${target.asmLabel})
fun translateEqualsArray(left: PtArrayIndexer, right: PtExpression) {
val constIndex = left.index.asConstInteger()
if(constIndex!=null) {
if(left.variable==null)
TODO("support for ptr indexing ${left.position}")
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varName = asmgen.asmVariableName(left.variable)
val varName = asmgen.asmVariableName(left.variable!!)
if(left.splitWords) {
return if(notEquals)
translateAYNotEquals("${varName}_lsb+$constIndex", "${varName}_msb+$constIndex")
@ -1596,13 +1719,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYNotEquals("#<$varname", "#>$varname")
@ -1648,13 +1771,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYEquals("#<$varname", "#>$varname")

View File

@ -15,18 +15,18 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
evalIfExpressionConditonAndBranchWhenFalse(expr.condition, falseLabel)
when {
expr.type.isByteOrBool -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
@ -195,7 +195,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp $varRight
bne +
@ -219,7 +219,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
cmp $varRight+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp $varRight
bne $falseLabel
@ -242,7 +242,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
beq $falseLabel
+""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out("""
cmp #<$number
bne +
@ -265,7 +265,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
cmp #>$number
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out( """
cmp #<$number
bne $falseLabel
@ -284,7 +284,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+1
beq $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | beq $falseLabel")
}
}
@ -299,7 +299,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
ora $varname+1
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY)
asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | bne $falseLabel")
}
}

View File

@ -45,7 +45,6 @@ internal class ProgramAndVarsGen(
}
memorySlabs()
tempVars()
footer()
}
}
@ -219,28 +218,6 @@ internal class ProgramAndVarsGen(
}
}
private fun tempVars() {
asmgen.out("; expression temp vars\n .section BSS")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.BYTE -> asmgen.out("$name .char ?")
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.WORD -> asmgen.out("$name .sint ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS")
}
private fun footer() {
var relocateBssVars = false
var relocateBssSlabs = false
@ -353,7 +330,9 @@ internal class ProgramAndVarsGen(
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
// the other variables that should be set to zero are done so as part of the BSS section.
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")
}
@ -370,7 +349,7 @@ internal class ProgramAndVarsGen(
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
@ -384,7 +363,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
@ -438,7 +417,7 @@ internal class ProgramAndVarsGen(
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
@ -456,7 +435,7 @@ internal class ProgramAndVarsGen(
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
entrypointInitialization()
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(functioncallAsmGen.optimizeIntArgsViaCpuRegisters(params)) {
asmgen.out("; simple int arg(s) passed via cpu register(s)")
@ -492,14 +471,14 @@ internal class ProgramAndVarsGen(
sub.children.forEach { asmgen.translate(it) }
asmgen.out("; variables")
asmgen.out(" .section BSS")
asmgen.out(" .section BSS_NOCLEAR") // these extra vars are initialized before use
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@ -508,11 +487,11 @@ internal class ProgramAndVarsGen(
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
asmgen.out(" .send BSS")
asmgen.out(" .send BSS_NOCLEAR")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
@ -567,12 +546,12 @@ internal class ProgramAndVarsGen(
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
outputStringvar(varname, 0, it.value.second, it.value.first)
outputStringvar(varname, 0u, it.value.second, it.value.first)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
arrayVariable2asm(varname, it.alloc.dt, 0, it.value, null)
arrayVariable2asm(varname, it.alloc.dt, 0u, it.value, null)
}
asmgen.out("+")
@ -636,22 +615,37 @@ internal class ProgramAndVarsGen(
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
if(varsNoInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables")
asmgen.out(" .section BSS")
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
val (dirty, clean) = varsNoInit.partition { it.dirty }
fun generate(section: String, variables: List<StStaticVariable>) {
asmgen.out(" .section $section")
val (notAligned, aligned) = variables.partition { it.align == 0u }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base })
.forEach { uninitializedVariable2asm(it) }
asmgen.out(" .send $section")
}
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
if(clean.isNotEmpty()) {
// clean vars end up in BSS so they're at least cleared to 0 at startup
generate("BSS", clean)
}
if(dirty.isNotEmpty()) {
// Dirty vars actually are ALSO are put into BSS so they're cleared to 0 at program startup,
// but NOT at each entry of the subroutine they're declared in.
// This saves the STZ's instructions in the subroutine, while still having deterministic start state.
// So there is no actual difference here when compared to the way non-dirty variables are allocated.
generate("BSS", dirty)
}
asmgen.out(" .send BSS")
}
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables with init value")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt.isString }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0 }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0 }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0u }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0u }
notAlignedStrings.forEach {
outputStringvar(
it.name,
@ -698,23 +692,27 @@ internal class ProgramAndVarsGen(
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
alignVar(variable.align)
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
}
dt.isArray -> {
alignVar(variable.align)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
asmgen.out("${variable.name}\t.fill $numbytes")
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
TODO("pointers are not supported yet")
}
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun alignVar(align: Int) {
if(align > 1)
private fun alignVar(align: UInt) {
if(align > 1u)
asmgen.out(" .align ${align.toHex()}")
}
@ -751,7 +749,7 @@ internal class ProgramAndVarsGen(
throw AssemblyError("all string vars should have been interned into prog")
}
dt.isArray -> {
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length)
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length?.toInt())
}
else -> {
throw AssemblyError("weird dt")
@ -759,7 +757,7 @@ internal class ProgramAndVarsGen(
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, align: Int, value: StArray?, orNumberOfZeros: Int?) {
private fun arrayVariable2asm(varname: String, dt: DataType, align: UInt, value: StArray?, orNumberOfZeros: Int?) {
alignVar(align)
when {
dt.isUnsignedByteArray || dt.isBoolArray -> {
@ -861,7 +859,7 @@ internal class ProgramAndVarsGen(
}
}
private fun outputStringvar(varname: String, align: Int, encoding: Encoding, value: String) {
private fun outputStringvar(varname: String, align: UInt, encoding: Encoding, value: String) {
alignVar(align)
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false)
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())

View File

@ -52,7 +52,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0 }
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0u }
require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
@ -60,9 +60,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@ -79,9 +79,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@ -92,16 +92,16 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// try to allocate the "don't care" interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) {
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedName }
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
for (variable in sortedList) {
if(variable.dt.isIntegerOrBool) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)

View File

@ -46,6 +46,12 @@ internal class AnyExprAsmGen(
}
return assignFloatBinExpr(expr, assign)
}
expr.type.isPointer -> {
require((expr.left.type.isPointer || expr.left.type.isUnsignedWord) && (expr.right.type.isPointer || expr.right.type.isUnsignedWord)) {
"both operands must be pointers or uwords"
}
throw AssemblyError("expression should have been handled otherwise: pointer ${expr.operator} at ${expr.position}")
}
else -> throw AssemblyError("weird expression type in assignment")
}
}
@ -53,7 +59,7 @@ internal class AnyExprAsmGen(
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1")
@ -61,7 +67,7 @@ internal class AnyExprAsmGen(
return true
}
"-" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1")
@ -76,7 +82,7 @@ internal class AnyExprAsmGen(
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | and P8ZP_SCRATCH_B1")
@ -84,7 +90,7 @@ internal class AnyExprAsmGen(
return true
}
"|" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | ora P8ZP_SCRATCH_B1")
@ -92,7 +98,7 @@ internal class AnyExprAsmGen(
return true
}
"^", "xor" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | eor P8ZP_SCRATCH_B1")

View File

@ -39,8 +39,11 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
}
init {
@ -160,8 +163,11 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
companion object {
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
@ -203,7 +209,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.returns.size>1)
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second

View File

@ -249,7 +249,12 @@ internal class AssignmentAsmGen(
SourceStorageKind.ARRAY -> {
val value = assign.source.array!!
val elementDt = assign.source.datatype
val arrayVarName = asmgen.asmVariableName(value.variable)
val valueVar = value.variable
if(valueVar==null) {
TODO("translate assignmenton pointer ${value.position}")
return
}
val arrayVarName = asmgen.asmVariableName(valueVar)
val constIndex = value.index.asConstInteger()
if(value.splitWords) {
@ -438,15 +443,21 @@ internal class AssignmentAsmGen(
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
val arrayDt = value.identifier.type
val sourceName =
if(value.isMsbForSplitArray)
asmgen.asmSymbolName(value.identifier) + "_msb"
else if(arrayDt.isSplitWordArray)
asmgen.asmSymbolName(value.identifier) + "_lsb" // the _lsb split array comes first in memory
else
asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName, value.isMsbForSplitArray, arrayDt, value.arrayIndexExpr)
val identifier = value.identifier
if(identifier==null) TODO("read &dereference")
else {
val source = asmgen.symbolTable.lookup(identifier.name)
require(source !is StConstant) { "addressOf of a constant should have been rewritten to a simple addition expression" }
val arrayDt = identifier.type
val sourceName =
if (value.isMsbForSplitArray)
asmgen.asmSymbolName(identifier) + "_msb"
else if (arrayDt.isSplitWordArray)
asmgen.asmSymbolName(identifier) + "_lsb" // the _lsb split array comes first in memory
else
asmgen.asmSymbolName(identifier)
assignAddressOf(assign.target, sourceName, value.isMsbForSplitArray, arrayDt, value.arrayIndexExpr)
}
}
is PtBool -> throw AssemblyError("source kind should have been literalboolean")
is PtNumber -> throw AssemblyError("source kind should have been literalnumber")
@ -604,7 +615,7 @@ internal class AssignmentAsmGen(
val symbol = asmgen.symbolTable.lookup(value.name)
val sub = symbol!!.astNode as IPtSubroutine
asmgen.translateFunctionCall(value)
if(sub is PtSub && sub.returns.size>1) {
if(sub is PtSub && sub.signature.returns.size>1) {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
val returnRegs = sub.returnsWhatWhere()
@ -920,12 +931,18 @@ internal class AssignmentAsmGen(
val tgt = PtAssignTarget(false, assign.target.position)
val targetarray = assign.target.array!!
val array = PtArrayIndexer(assign.target.datatype, targetarray.position)
array.add(targetarray.variable)
array.add(targetarray.index)
tgt.add(array)
assignTrue = PtAssignment(assign.position)
assignTrue.add(tgt)
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
val targetArrayVar = targetarray.variable
if(targetArrayVar==null) {
TODO("optimized comparison on pointer ${targetarray.position}")
} else {
array.add(targetArrayVar)
array.add(targetarray.index)
tgt.add(array)
assignTrue = PtAssignment(assign.position)
assignTrue.add(tgt)
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
}
}
TargetStorageKind.REGISTER -> { /* handled earlier */ return true }
TargetStorageKind.VOID -> { /* do nothing */ return true }
@ -971,7 +988,7 @@ internal class AssignmentAsmGen(
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.remainder_ub_asm")
if(target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
@ -1186,19 +1203,18 @@ internal class AssignmentAsmGen(
}
} else if(dt.isWord) {
if(shifts==7 && expr.operator == "<<") {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
asmgen.out("""
; shift left 7
sty P8ZP_SCRATCH_REG ; msb
sty P8ZP_SCRATCH_REG
lsr P8ZP_SCRATCH_REG
php ; save carry
ror a
sta P8ZP_SCRATCH_REG
lda #0
plp ; restore carry
ror P8ZP_SCRATCH_REG
ror a
ldy P8ZP_SCRATCH_REG""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@ -1311,25 +1327,35 @@ internal class AssignmentAsmGen(
val rightArrayIndexer = expr.right as? PtArrayIndexer
if(expr.operator=="+" && leftArrayIndexer!=null && leftArrayIndexer.type.isByte && right.type.isByte) {
// special optimization for bytearray[y] + bytevalue : no need to use a tempvar, just use adc array,y
assignExpressionToRegister(right, RegisterOrPair.A, right.type.isSigned)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(leftArrayIndexer.index, RegisterOrPair.Y, false)
if(!leftArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(leftArrayIndexer.variable)
asmgen.out(" clc | adc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt.isSigned, true)
val leftArrayVar = leftArrayIndexer.variable
if(leftArrayVar==null) {
TODO("optimized plusmin pointer ${leftArrayIndexer.position}")
} else {
assignExpressionToRegister(right, RegisterOrPair.A, right.type.isSigned)
if (!leftArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(leftArrayIndexer.index, RegisterOrPair.Y)
if (!leftArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(leftArrayVar)
asmgen.out(" clc | adc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt.isSigned, true)
}
} else if(rightArrayIndexer!=null && rightArrayIndexer.type.isByte && left.type.isByte) {
// special optimization for bytevalue +/- bytearray[y] : no need to use a tempvar, just use adc array,y or sbc array,y
assignExpressionToRegister(left, RegisterOrPair.A, left.type.isSigned)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y, false)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(rightArrayIndexer.variable)
if (expr.operator == "+")
asmgen.out(" clc | adc $arrayvarname,y")
else
asmgen.out(" sec | sbc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt.isSigned, true)
val rightArrayVar = rightArrayIndexer.variable
if(rightArrayVar==null) {
TODO("optimized plusmin pointer ${rightArrayIndexer.position}")
} else {
assignExpressionToRegister(left, RegisterOrPair.A, left.type.isSigned)
if (!rightArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y)
if (!rightArrayIndexer.index.isSimple()) asmgen.out(" pla")
val arrayvarname = asmgen.asmSymbolName(rightArrayVar)
if (expr.operator == "+")
asmgen.out(" clc | adc $arrayvarname,y")
else
asmgen.out(" sec | sbc $arrayvarname,y")
assignRegisterByte(target, CpuRegister.A, dt.isSigned, true)
}
} else if(expr.operator=="+" && leftMemByte!=null && right.type.isByte && optimizedPointerIndexPlusMinusByteIntoA(right, "+", leftMemByte)) {
assignRegisterByte(target, CpuRegister.A, dt.isSigned, true)
return true
@ -1355,7 +1381,7 @@ internal class AssignmentAsmGen(
return true
}
}
} else if(dt.isWord) {
} else if(dt.isWord || dt.isPointer) {
fun doAddOrSubWordExpr() {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
@ -1382,11 +1408,13 @@ internal class AssignmentAsmGen(
when (right) {
is PtAddressOf -> {
var symbol = asmgen.asmVariableName(right.identifier)
if(right.isFromArrayElement) {
TODO("address-of array element $symbol at ${right.position}")
TODO("address-of array element at ${right.position}")
} else if(right.dereference!=null) {
TODO("read &dereference")
} else {
if(right.identifier.type.isSplitWordArray) {
var symbol = asmgen.asmVariableName(right.identifier!!)
if(right.identifier!!.type.isSplitWordArray) {
symbol = if(right.isMsbForSplitArray) symbol+"_msb" else symbol+"_lsb"
}
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
@ -1567,8 +1595,8 @@ internal class AssignmentAsmGen(
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar)
if (constOffset != null && constOffset < 256) {
// we have value + @(zpptr + 255), or value - @(zpptr+255)
if (constOffset != null) {
// we have value + @(zpptr + 255), or value - @(zpptr+255). the offset is always <256.
asmgen.out(" ldy #$constOffset")
if (operator == "+")
asmgen.out(" clc | adc ($pointername),y")
@ -1613,10 +1641,16 @@ internal class AssignmentAsmGen(
}
val rightArray = expr.right as? PtArrayIndexer
if(rightArray!=null) {
val rightArrayVar = rightArray.variable
if(rightArrayVar==null) {
TODO("optimized bitwise pointer ${rightArray.position}")
return false
}
val constIndex = rightArray.index.asConstInteger()
if(constIndex!=null) {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
val valueVarname = "${asmgen.asmSymbolName(rightArray.variable)} + ${program.memsizer.memorySize(rightArray.type, constIndex)}"
val valueVarname = "${asmgen.asmSymbolName(rightArrayVar)} + ${program.memsizer.memorySize(rightArray.type, constIndex)}"
when(expr.operator) {
"&" -> asmgen.out(" and $valueVarname")
"|" -> asmgen.out(" ora $valueVarname")
@ -1725,12 +1759,17 @@ internal class AssignmentAsmGen(
is PtArrayIndexer -> {
val constIndex = right.index.asConstInteger()
if(constIndex!=null) {
val valueVarname = "${asmgen.asmSymbolName(right.variable)} + ${program.memsizer.memorySize(right.type, constIndex)}"
when(operator) {
"and" -> asmgen.out(" and $valueVarname")
"or" -> asmgen.out(" ora $valueVarname")
"xor" -> asmgen.out(" eor $valueVarname")
else -> throw AssemblyError("invalid logical operator")
val rightArrayVar = right.variable
if(rightArrayVar==null) {
TODO("assign result into A pointer ${right.position}")
} else {
val valueVarname = "${asmgen.asmSymbolName(rightArrayVar)} + ${program.memsizer.memorySize(right.type, constIndex)}"
when(operator) {
"and" -> asmgen.out(" and $valueVarname")
"or" -> asmgen.out(" ora $valueVarname")
"xor" -> asmgen.out(" eor $valueVarname")
else -> throw AssemblyError("invalid logical operator")
}
}
}
else assignViaScratch()
@ -1739,6 +1778,17 @@ internal class AssignmentAsmGen(
}
}
fun requiresCmp(expr: PtExpression) =
when (expr) {
is PtFunctionCall -> {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
}
is PtBuiltinFunctionCall -> true
is PtIfExpression -> true
else -> false
}
if(!expr.right.isSimple() && expr.operator!="xor") {
// shortcircuit evaluation into A
val shortcutLabel = asmgen.makeLabel("shortcut")
@ -1746,15 +1796,23 @@ internal class AssignmentAsmGen(
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" beq $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" bne $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
else -> throw AssemblyError("invalid logical operator")
@ -1960,7 +2018,7 @@ $endLabel""")
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
else -> DataType.UNDEFINED to 0u
}
when {
dt.isString -> {
@ -1968,7 +2026,7 @@ $endLabel""")
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position,"P8ZP_SCRATCH_W1"), symbolName, false, null, null)
asmgen.out(" pla")
asmgen.out(" ldy #${numElements-1}")
asmgen.out(" ldy #${numElements-1u}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
dt.isFloatArray -> {
@ -2123,7 +2181,7 @@ $endLabel""")
assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base)
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base)
}
@ -2276,6 +2334,12 @@ $endLabel""")
return
}
if(targetDt.isUnsignedWord && valueDt.isPointer) {
assignExpressionToRegister(value, RegisterOrPair.AY, false)
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
// No more special optimized 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..... :-/
@ -2601,58 +2665,118 @@ $endLabel""")
}
}
BaseDataType.STR -> throw AssemblyError("cannot typecast a string value")
BaseDataType.POINTER -> {
if(targetDt.isWord || targetDt.isPointer) {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
} else {
throw AssemblyError("cannot assign pointer to $targetDt")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
if(constIndex>0)
if(constIndex in 1..255)
asmgen.out("""
clc
adc #$constIndex
bcc +
iny
+""")
else if(constIndex>=256) {
asmgen.out("""
clc
adc #<$constIndex
pha
tya
adc #>$constIndex
tay
pla""")
}
}
else {
if(constIndex>0) {
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
} else {
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return
} else {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bcc +
iny
if(arrayIndexExpr.type.isWord) {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.AY, false)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pla
tay
pla
clc
adc P8ZP_SCRATCH_W1
pha
tya
adc P8ZP_SCRATCH_W1+1
tay
pla""")
}
else {
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bcc +
iny
+""")
}
}
else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
val subtype = arrayDt.sub!!
if(subtype.isByteOrBool) {
// elt size 1, we're good
} else if(subtype.isWord) {
if(!arrayDt.isSplitWordArray) {
// elt size 2
asmgen.out(" asl a")
}
} else if(subtype==BaseDataType.FLOAT) {
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${arrayIndexExpr.position}")
asmgen.out("""
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG"""
)
} else throw AssemblyError("weird type $subtype")
asmgen.out("""
ldy #>$arrayName
ldy #>$sourceName
clc
adc #<$arrayName
adc #<$sourceName
bcc +
iny
+""")
@ -2855,7 +2979,7 @@ $endLabel""")
jsr floats.MOVMF""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
@ -2887,7 +3011,7 @@ $endLabel""")
TargetStorageKind.ARRAY -> {
asmgen.out(" pha")
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" pla")
asmgen.out("""
@ -2924,7 +3048,7 @@ $endLabel""")
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
@ -3397,7 +3521,7 @@ $endLabel""")
} else {
require(target.array.index.type.isByteOrBool)
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y)
asmgen.out(" pla | sta ${target.asmVarname},y")
}
}
@ -3712,7 +3836,7 @@ $endLabel""")
asmgen.out(" stz ${target.asmVarname}+$indexValue")
}
else {
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.X, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.X)
asmgen.out(" stz ${target.asmVarname},x")
}
}
@ -3802,7 +3926,7 @@ $endLabel""")
sta ${target.asmVarname}+4""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
@ -3836,7 +3960,7 @@ $endLabel""")
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A)
asmgen.out("""
ldy #<${constFloat}
sty P8ZP_SCRATCH_W1
@ -4019,8 +4143,10 @@ $endLabel""")
addressOf != null -> {
if(addressOf.isFromArrayElement) {
TODO("address-of array element $addressOf")
} else if(addressOf.dereference!=null) {
throw AssemblyError("write &dereference, makes no sense at ${addressOf.position}")
} else {
asmgen.out(" sta ${asmgen.asmSymbolName(addressOf.identifier)}")
asmgen.out(" sta ${asmgen.asmSymbolName(addressOf.identifier!!)}")
}
}
addressExpr is PtIdentifier -> {

View File

@ -170,7 +170,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(memory.address is PtBinaryExpression && tryOptimizedMemoryInplace(memory.address as PtBinaryExpression, operator, value))
return
// slower method to calculate and use the pointer to access the memory with:
asmgen.assignExpressionToRegister(memory.address, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(memory.address, RegisterOrPair.AY)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.saveRegisterStack(CpuRegister.Y, true)
if(asmgen.isTargetCpu(CpuType.CPU65C02))
@ -205,7 +205,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
@ -224,7 +224,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.ARRAY -> {
val indexNum = target.array!!.index as? PtNumber
val targetArrayVar = target.array!!.variable
if(targetArrayVar==null) {
TODO("array indexing on pointer ${target.position}")
return
}
val indexNum = target.array.index as? PtNumber
if (indexNum!=null) {
val index = indexNum.number.toInt()
if(target.array.splitWords) {
@ -320,7 +325,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" lda ${targetArrayVar.name},y")
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
inplacemodificationRegisterAwithVariable(operator, "#${value.boolean!!.asInt()}", target.datatype.isSigned)
@ -356,7 +361,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
@ -366,7 +371,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda $tempVar")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
asmgen.out(" sta ${targetArrayVar.name},y")
}
target.datatype.isWord -> {
@ -377,11 +382,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
asmgen.out(" lda ${targetArrayVar.name}_lsb,y")
asmgen.out(" ldx ${targetArrayVar.name}_msb,y")
} else {
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
asmgen.out(" lda ${targetArrayVar.name},y")
asmgen.out(" ldx ${targetArrayVar.name}+1,y")
}
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
@ -439,7 +444,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
@ -450,14 +455,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(target.array.splitWords)
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
asmgen.out(" sta ${targetArrayVar.name}_lsb,y | txa | sta ${targetArrayVar.name}_msb,y")
else
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
asmgen.out(" sta ${targetArrayVar.name},y | txa | sta ${targetArrayVar.name}+1,y")
}
target.datatype.isFloat -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
@ -515,7 +520,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
val arrayvar = asmgen.asmVariableName(array.variable)
val arrayVar = array.variable
if(arrayVar==null) {
TODO("indexed inc/dec on pointer ${array.position}")
return false
}
val arrayvar = asmgen.asmVariableName(arrayVar)
when {
array.type.isByte -> {
asmgen.loadScaledArrayIndexIntoRegister(array, CpuRegister.X)
@ -583,7 +593,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(address.left is PtIdentifier && asmgen.isZpVar(address.left as PtIdentifier)) {
return (address.left as PtIdentifier).name
} else {
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY, false)
asmgen.assignExpressionToRegister(address.left, RegisterOrPair.AY)
asmgen.out(" sta P8ZP_SCRATCH_W2 | sty P8ZP_SCRATCH_W2+1")
return "P8ZP_SCRATCH_W2"
}
@ -635,9 +645,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return true
}
if(rightTc!=null)
asmgen.assignExpressionToRegister(rightTc.value, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(rightTc.value, RegisterOrPair.A)
else
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(address.right, RegisterOrPair.A)
asmgen.out(" pha") // offset on stack
val zpPointerVarName = addrIntoZpPointer()
assignValueToA()
@ -910,7 +920,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@ -1019,7 +1029,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
@ -1125,11 +1135,18 @@ $shortcutLabel:""")
}
if(value is PtArrayIndexer && value.isSimple()) {
val valueVar = value.variable
if(valueVar==null) {
TODO("inplace modification on pointer ${value.position}")
return
}
// use the already existing optimized codegen for regular assignments x += array[index]
val binexpr = PtBinaryExpression(operator, dt, value.position)
binexpr.add(PtIdentifier(name, dt, value.position))
val arrayValue = PtArrayIndexer(value.type, value.position)
arrayValue.add(value.variable)
arrayValue.add(valueVar)
arrayValue.add(value.index)
binexpr.add(arrayValue)
binexpr.parent = value
@ -1190,7 +1207,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@ -1363,7 +1380,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" tay | lda $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@ -1534,7 +1551,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@ -1921,18 +1938,16 @@ $shortcutLabel:""")
asmgen.out(" lda #0 | sta $lsb")
}
value==7 -> {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
asmgen.out("""
; shift left 7
lsr $msb
php ; save carry
lda $lsb
ror a
sta $msb
lda #0
sta $lsb
plp ; restore carry
ror $msb
ror $lsb""")
ror a
sta $lsb""")
}
value>3 -> asmgen.out("""
ldy #$value
@ -2468,7 +2483,7 @@ $shortcutLabel:""")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
// the value is a proper 16-bit word, so use both bytes of it.
when (operator) {
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
@ -2740,7 +2755,7 @@ $shortcutLabel:""")
"+" -> {
// name += byteexpression
if(valueDt.isUnsignedByte) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, false)
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
clc
adc $name
@ -2890,7 +2905,7 @@ $shortcutLabel:""")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
// the value is a proper 16-bit word, so use both bytes of it.
if(value is PtArrayIndexer && value.isSimple()) {
@ -2905,7 +2920,12 @@ $shortcutLabel:""")
"-" -> {
if(value.index.type.isByte) {
// it's an array indexed by a byte so we can use sbc array,y
val arrayname = value.variable.name
val valueVar = value.variable
if(valueVar==null) {
TODO("inplace modification on pointer ${value.position}")
return
}
val arrayname = valueVar.name
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
asmgen.out("""

View File

@ -5,7 +5,9 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray) {
require(numElements != null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@ -68,7 +70,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun print_single_error(errormessage: String) { /* prints nothing in tests */ }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@ -58,6 +58,7 @@ class TestCodegen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -67,6 +68,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@ -76,6 +78,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@ -85,6 +88,7 @@ class TestCodegen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY

View File

@ -1,10 +1,10 @@
package prog8.codegen.experimental
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
@ -26,7 +26,8 @@ class ExperiCodeGen: ICodeGeneratorBackend {
// this stub only writes the IR program to disk but doesn't generate anything else.
IRFileWriter(irProgram, null).write()
println("** experimental codegen stub: no assembly generated **")
if(!options.quiet)
println("** experimental codegen stub: no assembly generated **")
return EmptyProgram
}
}
@ -34,7 +35,8 @@ class ExperiCodeGen: ICodeGeneratorBackend {
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
println("** nothing assembled **")
if(!options.quiet)
println("** nothing assembled **")
return true
}

View File

@ -1,8 +1,10 @@
package prog8.codegen.intermediate
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.intermediate.*
@ -45,6 +47,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@ -78,7 +82,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
@ -91,7 +94,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentA = exprGen.translateExpression(call.args[2])
@ -143,7 +145,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@ -240,11 +241,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
BaseDataType.UWORD -> {
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
BaseDataType.FLOAT -> {
addToResult(result, tr, -1, tr.resultFpReg)
@ -278,7 +279,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])
@ -497,8 +497,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.BYTE, resultReg, -1)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcStructAlloc(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname)
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@ -548,7 +556,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
val variable = arr.variable.name
if(arr.variable==null)
TODO("support for ptr indexing ${arr.position}")
val variable = arr.variable!!.name
if(arr.splitWords) {
result += IRCodeChunk(null, null).also {
when(opcodeMemAndReg.first) {
@ -617,7 +627,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(target.variable==null)
TODO("support for ptr indexing ${target.position}")
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
@ -651,6 +663,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
else {
val targetVariable = target.variable
if(targetVariable==null)
TODO("support for ptr indexing ${target.position}")
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
@ -659,17 +675,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
} else {
@ -680,17 +696,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
}

View File

@ -1,14 +1,8 @@
package prog8.codegen.intermediate
import prog8.code.StExtSub
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StSub
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.Statusflag
import prog8.code.core.*
import prog8.intermediate.*
@ -92,12 +86,107 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
is PtFunctionCall -> translate(expr)
is PtContainmentCheck -> translate(expr)
is PtPointerDeref -> translate(expr)
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
}
}
private fun translate(deref: PtPointerDeref): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
var pointerReg: Int
if(deref.startpointer.type.isStructInstance) {
TODO("translate structinstance deref???")
/*
val arrayIndexer = deref.startpointer as? PtArrayIndexer
if(arrayIndexer==null)
throw AssemblyError("when start pointer is struct instance, array indexer is expected PTR[x]")
// first evaluate the adress of POINTER[x].a
// which is: value in pointer + x*sizeof(struct) + offsetof(a)
// then use traverseDerefChainToCalculateFinalAddress on b.c.d.field
val struct = deref.startpointer.type.subType as StStruct
val chain = ArrayDeque(deref.chain)
val firstField = struct.getField(chain.removeFirst(), codeGen.program.memsizer)
val pointerTr = translateExpression(arrayIndexer.variable)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
val constIndex = arrayIndexer.index.asConstInteger()
if(constIndex!=null) {
val offset = constIndex * struct.size.toInt()
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = offset), null)
} else {
val indexTr = translateExpression(arrayIndexer.index)
result += indexTr.chunks
// multiply the index by the size of the struct and add that to the pointer, then add the offset of the field,
// and retrieve the pointer value that is stored there, or the actual value if it's a pointer to simple type
result += IRCodeChunk(null, null).also {
val indexReg: Int
if(arrayIndexer.index.type.isByte) {
// extend array index to word
indexReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, indexReg, indexTr.resultReg)
} else {
indexReg = indexTr.resultReg
}
it += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt())
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerReg, reg2 = indexReg)
}
}
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = firstField.second.toInt())
if (firstField.first.isPointer) {
// get the address stored in the pointer and use that for the rest of the chain
// LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
} else {
require(chain.isEmpty())
// it's a pointer to a simple value, so keep the pointer as-is
}
}
// now use traverseDerefChainToCalculateFinalAddress on b.c.d and finally field or on b.c.d.field if field isn't a field.
val derefField = if(deref.type.isPointer) null else chain.removeLastOrNull()
actualDeref = PtPointerDeref(deref.type, chain, derefField, deref.position)
actualDeref.add(arrayIndexer.variable)
*/
} else {
val tr = translateExpression(deref.startpointer)
result += tr.chunks
pointerReg = tr.resultReg
}
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
result += instructions
if(offset==0u) {
val irdt = irType(deref.type)
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
// load field with offset
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val irdt = irType(deref.type)
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADFIELD, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
private fun translate(ifExpr: PtIfExpression): ExpressionCodeResult {
if((ifExpr.condition as? PtPrefix)?.operator=="not")
@ -166,46 +255,63 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(expr: PtAddressOf): ExpressionCodeResult {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(vmDt)
val identifier = expr.identifier
fun loadAddressOfArrayLabel(reg: Int) {
if (expr.isMsbForSplitArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_msb"), null)
} else if (expr.identifier.type.isSplitWordArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier!!.name + "_msb"), null)
} else if (identifier!!.type.isSplitWordArray) {
// the _lsb split array comes first in memory
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_lsb"), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name + "_lsb"), null)
} else
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name), null)
}
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null)
if(expr.identifier.type.isUnsignedWord) {
val indexWordReg = if(indexTr.dt==IRDataType.BYTE) {
val ixWord = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null)
ixWord
} else indexTr.resultReg
val resultRegister = codeGen.registers.next(vmDt)
if(identifier!!.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
val ptr = codeGen.symbolTable.lookup(identifier.name)
it += if(ptr is StConstant)
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
else
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
val eltSize = codeGen.program.memsizer.memorySize(identifier.type, 1)
result += IRCodeChunk(null, null).also {
loadAddressOfArrayLabel(resultRegister)
if(eltSize>1 && !expr.identifier.type.isSplitWordArray) {
it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1=indexWordReg, immediate = eltSize)
if (eltSize > 1 && !identifier.type.isSplitWordArray) {
it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1 = indexWordReg, immediate = eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = resultRegister, reg2 = indexWordReg)
}
}
} else {
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else if(expr.identifier!=null ) {
val resultRegister = codeGen.registers.next(vmDt)
loadAddressOfArrayLabel(resultRegister)
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else {
require(vmDt==IRDataType.WORD)
val pointerTr = translateExpression(expr.dereference!!.startpointer)
result += pointerTr.chunks
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg)
result += instructions
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset.toInt()), null)
return ExpressionCodeResult(result, vmDt, pointerTr.resultReg, -1)
}
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
private fun translate(mem: PtMemoryByte): ExpressionCodeResult {
@ -219,31 +325,32 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
val ptrWithOffset = mem.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// LOADIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=offsetReg, labelSymbol = ptrName)
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.LOADFIELD, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
val tr = translateExpression(mem.address)
addToResult(result, tr, tr.resultReg, -1)
@ -289,7 +396,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val haystackVar = check.haystackHeapVar!!
when {
haystackVar.type.isString -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@ -300,7 +406,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isByteArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@ -314,7 +419,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isWordArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@ -328,7 +432,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isFloatArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(haystackVar)
@ -346,11 +449,30 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(arrayIx: PtArrayIndexer): ExpressionCodeResult {
if(arrayIx.type.isStructInstance)
throw AssemblyError("cannot translate POINTER[x] resulting in a struct instance; this is likely part of a larger expression POINTER[x].field and that has to be translated earlier as a whole")
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type, null)
val vmDt = irType(arrayIx.type)
val result = mutableListOf<IRCodeChunkBase>()
val arrayVarSymbol = arrayIx.variable.name
val arrayVar = arrayIx.variable
if(arrayVar==null) {
val pointerTr = translateExpression(arrayIx.pointerderef!!)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
return translatePointerIndexing(result, pointerReg, arrayIx.index, eltSize, vmDt)
}
if(arrayVar.type.isPointer) {
val pointerTr = translateExpression(arrayVar)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
return translatePointerIndexing(result, pointerReg, arrayIx.index, eltSize, vmDt)
}
require(!arrayVar.type.isPointer) { "only regular array indexing here ${arrayIx.position}" }
var resultRegister = -1
var resultFpRegister = -1
val arrayVarSymbol = arrayVar.name
if(arrayIx.splitWords) {
require(vmDt==IRDataType.WORD)
@ -377,9 +499,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
}
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize)
fun indexByNumber(index: Int) {
val memOffset = index * eltSize
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
@ -388,23 +509,61 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
}
fun indexByExpression() {
val (code, indexByteReg) = codeGen.loadIndexReg(arrayIx.index, eltSize, false, arrayIx.splitWords)
result += code
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexByteReg, labelSymbol = arrayVarSymbol), null)
}
}
if(arrayIx.index is PtNumber)
indexByNumber((arrayIx.index as PtNumber).number.toInt())
else
indexByExpression()
return ExpressionCodeResult(result, vmDt, resultRegister, resultFpRegister)
}
private fun translatePointerIndexing(
result: MutableList<IRCodeChunkBase>,
pointerReg: Int,
index: PtExpression,
eltSize: Int,
resultDt: IRDataType
): ExpressionCodeResult {
var resultRegister = -1
var resultFpRegister = -1
if(index is PtNumber) {
val memOffset = eltSize * index.number.toInt()
if(memOffset>0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1=pointerReg, immediate = memOffset), null)
}
else {
val (code, indexWordReg) = codeGen.loadIndexReg(index, eltSize, true, false)
result += code
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=pointerReg, reg2=indexWordReg), null)
}
if(resultDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
}
else {
resultRegister = codeGen.registers.next(resultDt)
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg), null)
}
return ExpressionCodeResult(result, resultDt, resultRegister, resultFpRegister)
}
private fun translate(expr: PtPrefix): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(expr.value)
@ -445,9 +604,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isWord -> {
valueDt.isWord || valueDt.isPointer -> {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 =loadStatusAsBooleanResult(Opcode.BSTNE, result)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
@ -510,6 +669,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
BaseDataType.POINTER -> {
actualResultReg2 = tr.resultReg
}
else -> throw AssemblyError("weird cast value type")
}
}
@ -553,6 +715,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else -> throw AssemblyError("weird cast value type")
}
}
BaseDataType.POINTER -> {
require(valueDt.isUnsignedWord || valueDt.isPointer)
actualResultReg2 = tr.resultReg
// no further conversion required, pointers are all just uwords
}
BaseDataType.ARRAY_POINTER -> {
TODO("typecast to array of pointers $valueDt -> ${cast.type}")
}
else -> throw AssemblyError("weird cast type")
}
@ -565,8 +735,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
@ -581,6 +751,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
">" -> operatorGreaterThan(binExpr, vmDt, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, signed, true)
"." -> operatorDereference(binExpr, vmDt) // eww, nasty, would rather not have any such expressions anymore
else -> throw AssemblyError("weird operator ${binExpr.operator}")
}
}
@ -588,11 +759,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
if(callTarget.scopedNameString in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
return translateStackFunctions(fcall, callTarget)
}
when(callTarget.scopedName) {
when(callTarget.scopedNameString) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLC), null)
@ -618,7 +789,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (callTarget) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@ -642,7 +812,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
// return value(s)
// TODO: for current implemenation of the call convention in case of multiple return values,
// TODO: for current implementation of the call convention in case of multiple return values,
// a list of Ir virtual registers to hold the results is NOT correct (they're loaded into AY + R15..R0 instead!)
// So we use an empty list to avoid confusion here. This may change in a future version.
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
@ -670,7 +840,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StExtSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@ -709,9 +878,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
}
else {
TODO("callfar into another bank is not implemented for the selected compilation target")
} else if(address.constbank!=null) {
IRInstruction(
Opcode.CALLFAR,
address = address.address.toInt(),
immediate = address.constbank!!.toInt()
)
} else {
val tr = translateExpression(address.varbank!!)
require(tr.dt==IRDataType.BYTE)
result += tr.chunks
IRInstruction(
Opcode.CALLFARVB,
address = address.address.toInt(),
reg1 = tr.resultReg
)
}
}
addInstr(result, call, null)
@ -762,6 +943,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
}
is StStruct -> {
throw AssemblyError("stray struct constructor should have been removed (normally it can only occur as initialization expression for a pointer variable)")
}
else -> {
if(callTarget.type == StNodeType.LABEL) {
require(fcall.void)
@ -771,7 +955,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
else {
throw AssemblyError("invalid node type")
throw AssemblyError("invalid node type ${callTarget.type} at ${callTarget.astNode?.position}")
}
}
}
@ -804,7 +988,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
val chunk = mutableListOf<IRCodeChunkBase>()
when(callTarget.scopedName) {
when(callTarget.scopedNameString) {
"sys.push" -> {
// push byte
val tr = translateExpression(fcall.args.single())
@ -1039,7 +1223,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@ -1054,7 +1238,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@ -1214,7 +1398,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDivide(binExpr: PtBinaryExpression, vmDt: IRDataType, signed: Boolean): ExpressionCodeResult {
private fun operatorDivide(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==IRDataType.FLOAT) {
@ -1229,11 +1414,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, if(signed)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
, null)
addInstr(result, if(dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
, null)
return ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@ -1241,35 +1426,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, signed)
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, dt.isSigned)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(binExpr.right is PtNumber) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
addInstr(result, if (signed)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
, null)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
, null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, if (signed)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
, null)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
, null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}
}
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorMultiply(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorLeft = binExpr.left as? PtNumber
val constFactorRight = binExpr.right as? PtNumber
@ -1291,7 +1477,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@ -1299,20 +1485,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorLeft.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
val opcode = if(dt.isSigned) Opcode.MULSR else Opcode.MULR
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
@ -1428,6 +1615,83 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDereference(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
// the only case we support here is: a.b.c[i] . value
val left = binExpr.left as? PtArrayIndexer
val right = binExpr.right as? PtIdentifier
require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"}
val result = mutableListOf<IRCodeChunkBase>()
val indexedTr = translateExpression(left)
result += indexedTr.chunks
val pointerReg = indexedTr.resultReg
val struct = left.type.dereference().subType as? StStruct
require(indexedTr.dt== IRDataType.WORD && struct!=null)
val field = struct.getField(right.name, this.codeGen.program.memsizer)
var resultFpReg = -1
var resultReg = -1
if(vmDt==IRDataType.FLOAT)
resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
else
resultReg = codeGen.registers.next(vmDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = field.second.toInt())
if(vmDt==IRDataType.FLOAT)
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
it += IRInstruction(Opcode.LOADI, vmDt, reg1 = resultReg, reg2 = pointerReg)
}
return ExpressionCodeResult(result, vmDt, resultReg, resultFpReg)
}
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, UInt> {
// returns instructions to calculate the pointer address, and the offset into the struct it points to
// so that LOADFIELD and STOREFIELD opcodes can be used instead of having an explicit extra ADD
val result = mutableListOf<IRCodeChunkBase>()
if(targetPointerDeref.chain.isEmpty())
return result to 0u // nothing to do; there's no deref chain
var struct: StStruct? = null
if(targetPointerDeref.startpointer.type.subType!=null)
struct = targetPointerDeref.startpointer.type.subType as StStruct
if(targetPointerDeref.chain.isNotEmpty()) {
// traverse deref chain
for(deref in targetPointerDeref.chain.dropLast(1)) {
val fieldinfo = struct!!.getField(deref, codeGen.program.memsizer)
struct = fieldinfo.first.subType as StStruct
// get new pointer from field
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
}
}
}
val field = targetPointerDeref.chain.last()
val fieldinfo = struct!!.getField(field, codeGen.program.memsizer)
if(fieldinfo.second>0u) {
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
return result to 0u
} else {
return result to fieldinfo.second
}
}
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
}
return result to 0u
}
}

View File

@ -48,6 +48,7 @@ class IRCodeGen(
irProg.linkChunks()
irProg.convertAsmChunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()
@ -55,6 +56,8 @@ class IRCodeGen(
return irProg
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
// Normally, block level (global) variables that have a numeric initialization value
// are initialized via an assignment statement.
@ -64,15 +67,17 @@ class IRCodeGen(
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
val block = variable.parent.astNode as PtBlock
val initialization = (block.children.firstOrNull {
it is PtAssignment && it.target.identifier?.name==variable.scopedName
it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedNameString
} as PtAssignment?)
val initValue = initialization?.value
when(initValue){
is PtBool -> {
require(initValue.asInt()!=0) { "boolean var should not be initialized with false, it wil be set to false as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0) { "variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}
@ -101,6 +106,15 @@ class IRCodeGen(
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtPointerDeref -> require('.' in node.startpointer.name) { "node $node name is not scoped: ${node.startpointer.name}" }
is PtIdentifier -> {
if('.' !in node.name) {
// there is 1 case where the identifier is not scoped: if it's the value field name after a pointer array indexing.
val expr = node.parent as? PtBinaryExpression
if (expr?.operator != "." || expr.right !== node || expr.left !is PtArrayIndexer || !expr.left.type.isPointer)
require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
}
}
else -> { /* node has no name or is ok to have no dots in the name */ }
}
node.children.forEach { verifyPtNode(it) }
@ -194,10 +208,7 @@ class IRCodeGen(
old.fpReg1,
old.fpReg2,
immediate = immediateValue,
null,
address = addressValue,
null,
null
address = addressValue
)
}
}
@ -256,9 +267,11 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
is PtStructDecl -> emptyList()
is PtSubSignature -> emptyList()
else -> TODO("missing codegen for $node")
}
@ -420,35 +433,44 @@ class IRCodeGen(
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
require(choice.parent.children.last() === choice)
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
val branchLabel: String
if(onlyJumpLabel==null) {
choices.add(choiceLabel to choice)
branchLabel = choiceLabel
} else {
branchLabel = onlyJumpLabel
}
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel)
}
}
}
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
if(choices.isNotEmpty())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
if(!choice.isOnlyGotoOrReturn())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
}
@ -468,11 +490,12 @@ class IRCodeGen(
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val elementDt = irType(iterable.type.elementType())
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@ -480,9 +503,9 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
@ -491,10 +514,9 @@ class IRCodeGen(
result += jumpChunk
result += IRCodeChunk(endLabel, null)
}
iterable.type.isSplitWordArray -> {
iterable.type.isSplitWordArray || iterable.type.isPointerArray -> {
// iterate over lsb/msb split word array
val elementDt = iterable.type.elementType()
if(!elementDt.isWord)
if(elementDt!=IRDataType.WORD)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@ -504,7 +526,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
@ -548,7 +570,7 @@ class IRCodeGen(
val step = iterable.step.number.toInt()
if (step==0)
throw AssemblyError("step 0")
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@ -634,7 +656,7 @@ class IRCodeGen(
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
val loopLabel = createLabelName()
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@ -771,7 +793,7 @@ class IRCodeGen(
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
@ -789,38 +811,40 @@ class IRCodeGen(
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
}
return code
}
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
internal fun multiplyByConst(dt: DataType, reg: Int, factor: Int): IRCodeChunk {
val irdt = irType(dt)
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
code += IRInstruction(Opcode.LSL, irdt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
code += IRInstruction(Opcode.LSLN, irdt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = factor)
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
IRInstruction(opcode, irdt, reg1=reg, immediate = factor)
}
}
return code
}
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
internal fun multiplyByConstInplace(dt: IRDataType, signed: Boolean, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
@ -850,10 +874,11 @@ class IRCodeGen(
else {
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
@ -1070,7 +1095,7 @@ class IRCodeGen(
}
// evaluate jump address expression into a register and jump indirectly to it
val tr = expressionEval.translateExpression(goto.target)
for(i in tr.chunks.flatMap { it.instructions }) {
for(i in tr.chunks.flatMap { c -> c.instructions }) {
it += i
}
it += IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg)
@ -1653,7 +1678,7 @@ class IRCodeGen(
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtTypeCast -> {
require(cond.type.isBool && cond.value.type.isNumeric)
require(cond.type.isBool && (cond.value.type.isNumeric || cond.value.type.isPointer))
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
@ -1836,7 +1861,7 @@ class IRCodeGen(
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtAlign -> TODO("ir support for inline %align")
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returns, child.position)
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
for (subchild in child.children) {
translateNode(subchild).forEach { sub += it }
}
@ -1885,21 +1910,23 @@ class IRCodeGen(
}
}
}
is PtStructDecl -> { /* do nothing, should be found in the symbol table */ }
else -> TODO("weird block child node $child")
}
}
return irBlock
}
private fun translate(parameters: List<PtSubroutineParameter>): List<IRSubroutine.IRParam> {
private fun translateParameters(parameters: List<PtNode>): List<IRSubroutine.IRParam> {
val result = mutableListOf<IRSubroutine.IRParam>()
parameters.forEach {
it as PtSubroutineParameter
if(it.register==null) {
val flattenedName = it.definingISub()!!.name + "." + it.name
if (symbolTable.lookup(flattenedName) == null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
result += IRSubroutine.IRParam(flattenedName, orig.dt)
require('.' in it.name) { "even parameter names should have been made fully scoped by now" }
val orig = symbolTable.lookup(it.name) as? StStaticVariable
if (orig == null)
TODO("fix missing lookup for: ${it.name} parameter")
result += IRSubroutine.IRParam(it.name, orig.dt)
} else {
val reg = it.register
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
@ -1924,7 +1951,7 @@ class IRCodeGen(
internal fun isOne(expression: PtExpression): Boolean = (expression as? PtNumber)?.number==1.0 || (expression as? PtBool)?.value==true
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
internal fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
return IRCodeChunk(label, null).also {
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
@ -1935,9 +1962,7 @@ class IRCodeGen(
}
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
internal fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
val chunk = IRCodeChunk(null, null)
when(registerOrFlag.registerOrPair) {
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
@ -1954,10 +1979,83 @@ class IRCodeGen(
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
return chunk
}
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, UInt> {
// calculates the pointer address and returns the register it's in + remaining offset into the struct (so that LOADFIELD/STOREFIELD instructions can be used)
val pointerTr = expressionEval.translateExpression(deref.startpointer)
result += pointerTr.chunks
val (instructions, offset) = expressionEval.traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerTr.resultReg)
result += instructions
return pointerTr.resultReg to offset
}
internal fun storeValueAtPointersLocation(result: MutableList<IRCodeChunkBase>, addressReg: Int, offset: UInt, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) {
if(offset==0u) {
val irdt = irType(type)
val instr = if(type.isFloat) {
if (valueIsZero) IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = existingValueRegister, reg1 = addressReg)
} else {
if (valueIsZero) IRInstruction(Opcode.STOREZI, irdt, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, irdt, reg1 = existingValueRegister, reg2 = addressReg)
}
addInstr(result, instr, null)
return
}
// store with field offset
var valueRegister = existingValueRegister
val irdt = irType(type)
if(valueIsZero && valueRegister<0) {
if(type.isFloat) {
valueRegister = registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = valueRegister, immediateFp = 0.0), null)
} else {
valueRegister = registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOAD, irdt, reg1 = valueRegister, immediate = 0), null)
}
}
val instr = if (type.isFloat)
IRInstruction(Opcode.STOREFIELD, IRDataType.FLOAT, fpReg1 = valueRegister, reg1 = addressReg, immediate = offset.toInt())
else
IRInstruction(Opcode.STOREFIELD, irdt, reg1 = valueRegister, reg2 = addressReg, immediate = offset.toInt())
addInstr(result, instr, null)
}
internal fun loadIndexReg(index: PtExpression, itemsize: Int, wordIndex: Boolean, arrayIsSplitWords: Boolean): Pair<IRCodeChunks, Int> {
// returns the code to load the Index into the register, which is also returned.
require(index !is PtNumber) { "index should not be a constant number here, calling code should handle that in a more efficient way" }
val result = mutableListOf<IRCodeChunkBase>()
if(wordIndex) {
val tr = expressionEval.translateExpression(index)
addToResult(result, tr, tr.resultReg, -1)
var indexReg = tr.resultReg
if(tr.dt==IRDataType.BYTE) {
indexReg = registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexReg, reg2=tr.resultReg), null)
}
result += multiplyByConst(DataType.UWORD, indexReg, itemsize)
return Pair(result, indexReg)
}
// regular byte size index value.
val byteIndexTr = expressionEval.translateExpression(index)
addToResult(result, byteIndexTr, byteIndexTr.resultReg, -1)
if(itemsize==1 || arrayIsSplitWords)
return Pair(result, byteIndexTr.resultReg)
result += multiplyByConst(DataType.UBYTE, byteIndexTr.resultReg, itemsize)
return Pair(result, byteIndexTr.resultReg)
}
}

View File

@ -23,6 +23,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun optimizeOnlyJoinChunks() {
// this chunk-joining is REQUIRED (optimization or no) to end up with a structurally sound chunk list
irprog.foreachSub { sub ->
joinChunks(sub)
removeEmptyChunks(sub)
@ -84,7 +85,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original, otherwise merge both labels into 1.
If next chunk has label -> label name should be the same, in which case remove original, otherwise leave everything untouched.
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
@ -111,14 +112,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(index>0) {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
removeChunks += index
replaceLabels[chunk.label!!] = nextchunk.label!!
replaceLabels.entries.forEach { (key, value) ->
if (value == chunk.label)
replaceLabels[key] = nextchunk.label!!
}
}
}
}
}
@ -364,7 +357,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MULS, Opcode.MOD -> {
if (ins.immediate == 1) {
chunk.instructions.removeAt(idx)
changed = true

View File

@ -27,6 +27,9 @@ class IRUnusedCodeRemover(
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
// check if there are symbols referenced elsewhere that we should not prune (even though the rest of the block is empty)
blockVars.forEach { stVar ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
@ -47,7 +50,50 @@ class IRUnusedCodeRemover(
}
}
val blockStructs = irprog.st.allStructDefs().filter { it.name.startsWith(prefix) }
blockStructs.forEach { struct ->
irprog.st.allStructInstances().forEach { instance ->
if(instance.structName == struct.name)
return // a struct instance is declared using this struct type
}
irprog.st.allVariables().forEach { variable ->
if(variable.dt.isPointer || variable.dt.isStructInstance)
if(struct.name == variable.dt.subType!!.scopedNameString)
return // a variable exists with the struct as (pointer) type
}
}
irprog.st.removeTree(blockLabel)
removeBlockInits(irprog, blockLabel)
}
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
if(ins.reg1!=0) {
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(ins.fpReg1!=0) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
}
}
}
private fun removeUnusedSubroutines(): Int {

View File

@ -14,6 +14,12 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
StNodeType.STRUCTINSTANCE -> {
val instance = it.value as StStructInstance
val struct = sourceSt.lookup(instance.structName) as StStruct
st.add(convert(instance, struct.fields))
}
StNodeType.STRUCT -> st.add(convert(it.value as StStruct))
else -> { }
}
}
@ -37,12 +43,17 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
else
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
private fun convert(variable: StStaticVariable): IRStStaticVariable {
if('.' in variable.name) {
return IRStStaticVariable(variable.name,
@ -52,7 +63,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align)
variable.align,
variable.dirty)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
@ -61,14 +73,14 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedName))
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
val scopedName = variable.scopedName
val scopedName = variable.scopedNameString
return IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
@ -76,7 +88,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align
variable.align,
variable.dirty
)
}
}
@ -92,7 +105,7 @@ private fun convert(variable: StMemVar): IRStMemVar {
)
} else {
val scopedName = try {
variable.scopedName
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
@ -107,7 +120,7 @@ private fun convert(constant: StConstant): IRStConstant {
constant.name
} else {
try {
constant.scopedName
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
@ -120,12 +133,17 @@ private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
else
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align)
}
/*
private fun convert(instance: StStructInstance, fields: List<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
}
*/
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?

View File

@ -3,7 +3,9 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@ -66,7 +68,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun print_single_error(errormessage: String) { /* prints nothing in tests */ }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@ -53,7 +53,7 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().single().instructions.size shouldBe 1
}
test("remove jmp to label below") {
test("remove jmp to label below but keep labels") {
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
val c2 = IRCodeChunk("label", null)
@ -69,13 +69,16 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 3
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label2"
irProg.chunks()[2].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe false
irProg.chunks()[2].isEmpty() shouldBe true
val chunks = irProg.chunks()
chunks.size shouldBe 4
chunks[0].label shouldBe "main.start"
chunks[1].label shouldBe "label"
chunks[2].label shouldBe "label2"
chunks[3].label shouldBe "label3"
chunks[0].isEmpty() shouldBe true
chunks[1].isEmpty() shouldBe true
chunks[2].isEmpty() shouldBe false
chunks[3].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP

View File

@ -52,6 +52,7 @@ class TestVmCodeGen: FunSpec({
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -61,6 +62,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@ -70,6 +72,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@ -79,6 +82,7 @@ class TestVmCodeGen: FunSpec({
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -167,6 +171,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -238,6 +243,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -305,6 +311,7 @@ class TestVmCodeGen: FunSpec({
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -360,6 +367,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -431,6 +439,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -498,6 +507,7 @@ class TestVmCodeGen: FunSpec({
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
@ -554,11 +564,8 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
irChunks[0].instructions.size shouldBe 1
val callInstr = irChunks[0].instructions[0]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}

View File

@ -5,10 +5,7 @@ import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.BaseDataType
import prog8.code.core.Position
import prog8.code.core.isInteger
import prog8.code.core.isIntegerOrBool
import prog8.code.core.*
import kotlin.math.*
@ -70,52 +67,45 @@ class ConstExprEvaluator {
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
}
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() or right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
}
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type==BaseDataType.UBYTE || left.type==BaseDataType.BOOL) {
if(right.type.isIntegerOrBool) {
return NumericLiteral(BaseDataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type==BaseDataType.UWORD) {
if(right.type.isInteger) {
return NumericLiteral(BaseDataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type==BaseDataType.LONG) {
if(right.type.isInteger) {
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
if(right.type.isIntegerOrBool) {
val leftDt = left.type
if(leftDt.isByteOrBool)
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() and right.number.toInt()) and 255).toDouble(), left.position)
else if(leftDt.isInteger) {
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
else if (leftDt == BaseDataType.LONG)
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)

View File

@ -27,8 +27,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
if(addrOf.identifier?.inferType(program)?.isBytes==true)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier!!, parent))
}
return noModifications
}
@ -129,7 +129,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
}
else {
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program)
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
}
@ -332,15 +332,19 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val constIndex = arrayIndexedExpression.indexer.constIndex()
if (constIndex != null) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null) {
val array =arrayVar.value as? ArrayLiteral
if(array!=null) {
val value = array.value[constIndex].constValue(program)
if(value!=null) {
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
if(arrayIndexedExpression.plainarrayvar!=null) {
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(arrayVar!=null) {
val array =arrayVar.value as? ArrayLiteral
if(array!=null) {
val value = array.value[constIndex].constValue(program)
if(value!=null) {
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
}
}
}
} else if(arrayIndexedExpression.pointerderef!=null) {
TODO("constant fold pointer[i]")
}
}
}
@ -387,10 +391,10 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val rangeTo = iterableRange.to as? NumericLiteral
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: return noModifications
val loopvar = forLoop.loopVar.targetVarDecl() ?: return noModifications
val stepLiteral = iterableRange.step as? NumericLiteral
require(loopvar.datatype.sub == null)
require(loopvar.datatype.isBasic)
val loopvarSimpleDt = loopvar.datatype.base
when(loopvarSimpleDt) {
BaseDataType.UBYTE -> {

View File

@ -323,7 +323,7 @@ internal class ConstantIdentifierReplacer(
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)

View File

@ -18,8 +18,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type, typecast.implicit)
if (literal != null && typecast.type.isBasic) {
val newLiteral = literal.cast(typecast.type.base, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
@ -33,7 +33,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program) issimpletype typecast.type) {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
@ -49,7 +49,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
@ -57,7 +57,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
@ -423,6 +423,29 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
if(dt.isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
}
}
val ptrDeref = arrayIndexedExpression.pointerderef
if(ptrDeref!=null) {
val dt = ptrDeref.inferType(program).getOrUndef()
if(dt.isPointer) {
// ptr1.ptr2[0] --> ptr1.ptr2^^
val deref = PtrDereference(ptrDeref.chain, true, ptrDeref.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, deref, parent))
}
}
}
return noModifications
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
// NOTE: only when the terms are not function calls!!!
if(expr.left is IFunctionCall || expr.right is IFunctionCall)
@ -499,7 +522,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (valueDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg.expression, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
} else {
@ -516,7 +539,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (argDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
@ -555,12 +578,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(functionCallExpr.target.nameInSource == listOf("mkword")) {
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
// just cast the lsb to uword
val cast = TypecastExpression(functionCallExpr.args[1], BaseDataType.UWORD, true, functionCallExpr.position)
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
else if(functionCallExpr.target.nameInSource == listOf("strings", "contains")) {
val target = (functionCallExpr.args[0] as? IdentifierReference)?.targetVarDecl(program)
val target = (functionCallExpr.args[0] as? IdentifierReference)?.targetVarDecl()
if(target?.value is StringLiteral) {
errors.info("for actual strings, use a regular containment check instead: 'char in string'", functionCallExpr.position)
val contains = ContainmentCheck(functionCallExpr.args[1], functionCallExpr.args[0], functionCallExpr.position)
@ -711,9 +734,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// just use: msb(value) as type
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return if(leftDt.isSignedWord)
TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
TypecastExpression(msb, DataType.BYTE, true, expr.position)
else
TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else -> return null
}
@ -839,14 +862,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
}
else -> {
@ -887,12 +910,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount==8) {
// shift right by 8 bits is just a byte operation: msb(X) as uword
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), BaseDataType.UWORD, true, expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
}
}
BaseDataType.WORD -> {
@ -903,12 +926,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount == 8) {
// shift right by 8 bits is just a byte operation: msb(X) as byte (will get converted to word later)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
return TypecastExpression(msb, DataType.BYTE, true, expr.position)
}
else if(amount > 8) {
// same as above but with residual shifts. Take care to do signed shift.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
val signed = TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
val signed = TypecastExpression(msb, DataType.BYTE, true, expr.position)
return BinaryExpression(signed, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
}
}

View File

@ -16,7 +16,7 @@ import prog8.code.target.VMTarget
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
// inliner potentially enables *ONE LINED* subroutines, without to be inlined.
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
@ -105,7 +105,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (subroutine.inline && subroutine.statements.size > 1) {
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
subroutine.statements.removeLastOrNull() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
}
@ -113,7 +113,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(identifier: IdentifierReference) {
identifier.targetStatement(program)?.let { target ->
identifier.targetStatement()?.let { target ->
val scoped = (target as INamedStatement).scopedName
val scopedIdent = IdentifierReference(scoped, identifier.position)
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
@ -122,7 +122,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: FunctionCallStatement) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
call.target.targetSubroutine()?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
@ -134,7 +134,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: FunctionCallExpression) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
call.target.targetSubroutine()?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
@ -149,7 +149,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
when (it) {
is NumericLiteral -> it.copy()
is IdentifierReference -> {
val target = it.targetStatement(program) ?: return emptyList()
val target = it.targetStatement() ?: return emptyList()
val scoped = (target as INamedStatement).scopedName
IdentifierReference(scoped, it.position)
}
@ -205,7 +205,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
val sub = functionCallStatement.target.targetStatement(program.builtinFunctions) as? Subroutine
return if(sub==null || !canInline(sub, functionCallStatement))
noModifications
else
@ -213,7 +213,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty() && canInline(sub, functionCallExpr)) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
@ -249,7 +249,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val stmt = sub.statements.single()
if (stmt is IFunctionCall) {
val existing = (fcall as Node).definingScope.lookup(stmt.target.nameInSource.take(1))
return existing !is VarDecl
return existing !is VarDecl && existing !is StructFieldRef
}
}
return true

View File

@ -29,12 +29,19 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
if(arg.arrayIndex==null) arg.identifier else null
if(arg.arrayIndex==null) {
if(arg.identifier!=null)
arg.identifier
else
null // struct can't have string fields so nothing to look at here
} else {
null
}
} else {
arg as? IdentifierReference
}
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteral
if(stringVar!=null && stringVar.wasStringLiteral()) {
val string = stringVar.targetVarDecl()?.value as? StringLiteral
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
@ -82,12 +89,11 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
)
}
@ -106,7 +112,7 @@ class StatementOptimizer(private val program: Program,
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
@ -146,13 +152,13 @@ class StatementOptimizer(private val program: Program,
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl()
if(iterable!=null) {
if(iterable.datatype.isString) {
val sv = iterable.value as StringLiteral
@ -161,8 +167,8 @@ class StatementOptimizer(private val program: Program,
// loop over string of length 1 -> just assign the single character
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, position=forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@ -173,9 +179,9 @@ class StatementOptimizer(private val program: Program,
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignTarget(forLoop.loopVar, null, null, null, false, position = forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@ -466,7 +472,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
@ -501,6 +507,27 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
} else {
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
}
}
}
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
}
return noModifications
}

View File

@ -8,9 +8,9 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.compiler.CallGraph
@ -27,10 +27,10 @@ class UnusedCodeRemover(private val program: Program,
program.allBlocks.singleOrNull { it.name=="sys" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pushw = subroutines.single { it.name == "pushw" }
val pop = subroutines.single { it.name == "pop" }
val popw = subroutines.single { it.name == "popw" }
val push = subroutines.single { s -> s.name == "push" }
val pushw = subroutines.single { s -> s.name == "pushw" }
val pop = subroutines.single { s -> s.name == "pop" }
val popw = subroutines.single { s -> s.name == "popw" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pushw)
neverRemoveSubroutines.add(pop)
@ -39,8 +39,8 @@ class UnusedCodeRemover(private val program: Program,
program.allBlocks.singleOrNull { it.name=="floats" } ?.let {
val subroutines = it.statements.filterIsInstance<Subroutine>()
val push = subroutines.single { it.name == "push" }
val pop = subroutines.single { it.name == "pop" }
val push = subroutines.single { s -> s.name == "push" }
val pop = subroutines.single { s -> s.name == "pop" }
neverRemoveSubroutines.add(push)
neverRemoveSubroutines.add(pop)
}
@ -181,14 +181,20 @@ class UnusedCodeRemover(private val program: Program,
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
if("ignore_unused" !in decl.definingBlock.options())
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
val callStruct = (assignment.value as IFunctionCall).target.targetStructDecl()
if(callStruct!=null) {
// don't turn a struct instance allocation call to a void call, instead, remove everything
return listOf(IAstModification.Remove(assignment, assignment.parent as IStatementContainer))
} else {
if("ignore_unused" !in decl.definingBlock.options())
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
}
} else {

View File

@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("application")
kotlin("jvm")
@ -18,7 +16,6 @@ dependencies {
implementation(project(":codeGenExperimental"))
implementation(project(":virtualmachine"))
// implementation(project(":beanshell"))
implementation("org.antlr:antlr4-runtime:4.13.2")
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6")

View File

@ -25,6 +25,5 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
</component>
</module>

View File

@ -419,12 +419,14 @@ sys {
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -946,6 +948,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@ -439,12 +439,14 @@ sys {
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -968,6 +970,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@ -28,12 +28,20 @@
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
; - call current() to get the current task id.
; - call kill(taskid) to kill a task by id.
; - call killall() to kill all tasks.
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
;
; TIP: HOW TO WAIT without BLOCKING other coroutines?
; Make sure you call yield() in the waiting loop, for example:
; uword timer = cbm.RDTIM16() + 60
; while cbm.RDTIM16() != timer
; void coroutines.yield()
coroutines {
%option ignore_unused
@ -41,19 +49,17 @@ coroutines {
const ubyte MAX_TASKS = 64
uword[MAX_TASKS] tasklist
uword[MAX_TASKS] userdatas
uword[MAX_TASKS] returnaddresses
ubyte active_task
uword supervisor
sub add(uword taskaddress, uword userdata) -> ubyte {
sub add(uword @nozp taskaddress, uword @nozp userdata) -> ubyte {
; find the next empty slot in the tasklist and stick it there
; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id!
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
for cx16.r0L in 0 to len(tasklist)-1 {
if tasklist[cx16.r0L] == 0 {
tasklist[cx16.r0L] = taskaddress
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
userdatas[cx16.r0L] = userdata
returnaddresses[cx16.r0L] = 0
sys.set_carry()
return cx16.r0L
}
@ -70,57 +76,48 @@ coroutines {
}
}
sub run(uword supervisor_routine) {
sub run(uword @nozp supervisor_routine) {
supervisor = supervisor_routine
for active_task in 0 to len(tasklist)-1 {
if tasklist[active_task]!=0 {
; activate the termination handler and start the first task
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(&termination)
goto tasklist[active_task]
sys.pushw(tasklist[active_task])
return
}
}
}
sub yield() -> uword {
; Store the return address of the yielding task,
; and continue with the next one instead (round-robin)
; Returns the associated userdata value
uword task_start, task_continue
returnaddresses[active_task] = sys.popw()
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
; Returns the associated userdata value.
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
uword task_return_address
tasklist[active_task] = sys.popw()
resume_with_next_task:
skip_task:
if not next_task() {
void sys.popw() ; remove return to the termination handler
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
return 0 ; exiting here will now actually return back to the calling program that called run()
}
if supervisor!=0 {
if supervisor!=0
if lsb(call(supervisor))==0
goto resume_with_next_task
}
goto skip_task
if task_continue==0 {
; fetch start address of next task.
; address on the stack must be pushed in reverse byte order
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(task_start)
} else
sys.pushw(task_continue)
; returning from yield then continues with the next coroutine
; returning from yield then continues with the next coroutine:
sys.pushw(task_return_address)
return userdatas[active_task]
sub next_task() -> bool {
; search through the task list for the next active task
repeat len(tasklist) {
active_task++
if active_task==len(returnaddresses)
if active_task==len(tasklist)
active_task=0
task_start = tasklist[active_task]
if task_start!=0 {
task_continue = returnaddresses[active_task]
task_return_address = tasklist[active_task]
if task_return_address!=0 {
return true
}
}
@ -128,9 +125,8 @@ resume_with_next_task:
}
}
sub kill(ubyte taskid) {
sub kill(ubyte @nozp taskid) {
tasklist[taskid] = 0
returnaddresses[taskid] = 0
}
sub current() -> ubyte {
@ -138,12 +134,10 @@ resume_with_next_task:
}
sub termination() {
; a task has terminated. wipe it from the list.
; this is an internal routine
; internal routine: a task has terminated. wipe it from the list.
kill(active_task)
; reactivate this termination handler
; note: cannot use pushw() because JSR doesn't push the return address in the same way
; reactivate this termination handler and go to the next task
sys.push_returnaddress(&termination)
goto coroutines.yield.resume_with_next_task
goto coroutines.yield.skip_task
}
}

View File

@ -22,18 +22,23 @@ monogfx {
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
uword buffer_visible, buffer_back
sub lores() {
; enable 320*240 bitmap mode
buffer_visible = buffer_back = $0000
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
cx16.VERA_L1_TILEBASE = 0 ; lores
width = 320
height = 240
lores_mode = true
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
@ -46,14 +51,40 @@ monogfx {
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
cx16.VERA_L1_TILEBASE = %00000001 ; hires
width = 640
height = 480
lores_mode = false
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
sub enable_doublebuffer() {
; enable double buffering mode
if lores_mode {
buffer_visible = $0000
buffer_back = $2800
} else {
buffer_visible = $0000
buffer_back = $9800
}
}
sub swap_buffers(bool wait_for_vsync) {
; flip the buffers: make the back buffer visible and the other one now the backbuffer.
; to avoid any screen tearing it is advised to call this during the vertical blank (pass true)
if wait_for_vsync
sys.waitvsync()
cx16.r0 = buffer_back
buffer_back = buffer_visible
buffer_visible = cx16.r0
cx16.VERA_CTRL = 0
cx16.r0 &= %1111110000000000
cx16.VERA_L1_TILEBASE = cx16.VERA_L1_TILEBASE & 1 | (cx16.r0H >>1 )
}
sub textmode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
@ -559,6 +590,7 @@ drawmode: ora cx16.r15L
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@ -597,6 +629,7 @@ drawmode: ora cx16.r15L
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@ -672,8 +705,7 @@ nostipple:
invert:
prepare()
%asm {{
lda cx16.VERA_DATA0
eor p8v_maskbits,y
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
}}
return
@ -696,7 +728,7 @@ invert:
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda p8v_xx
@ -708,18 +740,29 @@ invert:
; width=640 (hires)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx
and #7
pha ; xbits
; xx /= 8
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx
}}
xx /= 8
;xx /= 8
xx += yy*(640/8)
%asm {{
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
plx ; xbits
lda p8v_maskbits,x
}}
@ -761,11 +804,15 @@ invert:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@ -848,8 +895,8 @@ skip:
}
sub fill_scanline_right() {
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
while xx <= width-1 {
if pgetset()
break
@ -884,11 +931,15 @@ skip:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xpos+1
sta cx16.VERA_ADDR_M
lda p8v_xpos
sta cx16.VERA_ADDR_L
lda p8v_xpos+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@ -935,12 +986,12 @@ _doplot beq +
ror a
lsr a
lsr a
clc
ldy p8v_yy
clc
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00010000 ; autoincr
sta cx16.VERA_ADDR_H
@ -948,7 +999,33 @@ _doplot beq +
}
else {
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
;cx16.r0 += xx/8
%asm {{
ldy p8v_xx+1
lda p8v_xx
sty P8ZP_SCRATCH_B1
lsr P8ZP_SCRATCH_B1
ror a
lsr P8ZP_SCRATCH_B1
ror a
lsr a
clc
adc cx16.r0
sta cx16.r0
bcc +
inc cx16.r0+1
+
stz cx16.VERA_CTRL
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.r0H
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00001000 ; autoincr (1 bit shifted)
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
}}
}
return
}
@ -1116,15 +1193,11 @@ cdraw_mod2 ora cx16.VERA_DATA1
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
+ ldy #40
-
.rept 16
sta cx16.VERA_DATA0
.endrept
dey
bne -
rts

View File

@ -474,7 +474,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
extsub $C000 = x16edit_default() clobbers(A,X,Y)
@ -1168,8 +1168,39 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
sub joysticks_detect() -> ubyte {
; returns bits 0-4, set to 1 if that joystick is present.
; bit 0 = keyboard joystick, bit 1 - 4 = joypads 1 to 4
cx16.r0H = 255
for cx16.r0L in 4 downto 0 {
void cx16.joystick_get(cx16.r0L)
%asm {{
cpy #1 ; present?
}}
rol(cx16.r0H)
}
return ~cx16.r0H
}
; Commander X16 IRQ dispatcher routines
sub joysticks_getall(bool also_keyboard_js) -> uword {
; returns combined pressed buttons from all connected joysticks
; note: returns the 'normal' not inverted status bits for the buttons (1 = button pressed.)
cx16.r0H = 1
if also_keyboard_js
cx16.r0H = 0
cx16.r1 = $ffff
for cx16.r0L in cx16.r0H to 4 {
bool notpresent
cx16.r2, notpresent = cx16.joystick_get(cx16.r0L)
if not notpresent {
cx16.r1 &= cx16.r2
}
}
return ~cx16.r1
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
@ -1216,6 +1247,7 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
@ -1224,44 +1256,42 @@ _irq_dispatcher
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bit #2
stz _continue_with_system_handler
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
beq +
pha
jsr _line_handler
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
jsr _sprcol_handler
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
jsr _aflow_handler
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
pha
jsr _vsync_handler
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
tsb _continue_with_system_handler
pla
+ lsr a
lsr a
bcc +
pha
jsr _sprcol_handler
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
jsr _aflow_handler
tsb _continue_with_system_handler
+ jsr sys.restore_prog8_internals
lda _continue_with_system_handler
beq _no_sys_handler
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
_no_sys_handler
ply
plx
pla
@ -1485,12 +1515,14 @@ sys {
const ubyte target = 16 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -1997,6 +2029,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@ -119,6 +119,7 @@ verafx {
; Returns the 16 bits unsigned result of R0*R1 in AY.
; Note: only the lower 16 bits! (the upper 16 bits are not valid for unsigned word multiplications, only for signed)
; Verafx doesn't support unsigned values like this for full 32 bit result.
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda cx16.r0
sta P8ZP_SCRATCH_W1
@ -136,6 +137,7 @@ verafx {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25

View File

@ -241,6 +241,28 @@ divmod_ub_asm .proc
rts
.pend
remainder_ub_asm .proc
; -- divide A by Y, returns remainder in A (unsigned)
; division by zero will result in just the original number.
; This routine specialcases 0,1,2 and otherwise is just a repeated subtraction.
cpy #0
beq _zero
cpy #1
bne +
lda #0
rts
+ cpy #2
bne +
and #1
rts
+ sty P8ZP_SCRATCH_REG
sec
- sbc P8ZP_SCRATCH_REG
bcs -
adc P8ZP_SCRATCH_REG
_zero rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2

View File

@ -102,43 +102,35 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias).
; NOTE: does not work for code in ROM
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: does not work for code in ROM, use randrange_rom instead for that
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd() * (n as uword)
return cx16.r0H
}
sub randrange_rom(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias).
; NOTE: works for code in ROM, make sure to initialize seed using rndseed_rom
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd_rom()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: works for code in ROM, make sure you have initialized the seed using rndseed_rom
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd_rom() * (n as uword)
return cx16.r0H
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
; -- return random number uniformly distributed from 0 to n-1
; NOTE: does not work for code in ROM
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw() * n
return math.mul16_last_upper()
}
sub randrangew_rom(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
; NOTE: works for code in ROM, make sure to initialize seed using rndseed_rom
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw_rom()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; -- return random number uniformly distributed from 0 to n-1
; NOTE: works for code in ROM, make sure you have initialized the seed using rndseed_rom
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw_rom() * n
return math.mul16_last_upper()
}
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
@ -705,25 +697,36 @@ log2_tab
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@ -98,12 +98,14 @@ sys {
const ubyte target = 32 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 0 ; undefined, no floats supported
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -481,6 +483,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@ -43,46 +43,72 @@ _done
}}
}
/*
prog8 source code for the above routine:
sub gnomesort_ub(uword @requirezp values, ubyte num_elements) {
sub gnomesort_by_ub(uword @requirezp uw_keys, uword values, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
ubyte @zp pos=1
while pos != num_elements {
if values[pos]>=values[pos-1]
if uw_keys[pos]>=uw_keys[pos-1]
pos++
else {
; swap elements
cx16.r0L = values[pos-1]
values[pos-1] = values[pos]
values[pos] = cx16.r0L
cx16.r0L = uw_keys[pos-1]
uw_keys[pos-1] = uw_keys[pos]
uw_keys[pos] = cx16.r0L
uword @requirezp vptr = values + pos*$0002 -2
cx16.r0 = peekw(vptr)
pokew(vptr, peekw(vptr+2))
pokew(vptr+2, cx16.r0)
pos--
if_z
pos++
}
}
}
*/
sub gnomesort_uw(uword values, ubyte num_elements) {
; TODO optimize this more, rewrite in asm?
ubyte @zp pos = 1
uword @requirezp ptr = values+2
sub gnomesort_uw(uword @requirezp values, ubyte num_elements) {
; Sorts the values array (no-split unsigned words).
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r0 = peekw(ptr-2)
cx16.r1 = peekw(ptr)
if cx16.r0<=cx16.r1 {
pos++
ptr+=2
}
cx16.r1L = pos-2
if peekw(values+pos) >= peekw(values + cx16.r1L)
pos += 2
else {
; swap elements
pokew(ptr-2, cx16.r1)
pokew(ptr, cx16.r0)
if pos>1 {
pos--
ptr-=2
}
cx16.r0 = peekw(values + cx16.r1L)
pokew(values + cx16.r1L, peekw(values + pos))
pokew(values + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r1L = pos-2
if peekw(uw_keys+pos) >= peekw(uw_keys + cx16.r1L)
pos += 2
else {
; swap elements
cx16.r0 = peekw(uw_keys + cx16.r1L)
pokew(uw_keys + cx16.r1L, peekw(uw_keys+ pos))
pokew(uw_keys + pos, cx16.r0)
cx16.r0 = peekw(wordvalues + cx16.r1L)
pokew(wordvalues + cx16.r1L, peekw(wordvalues + pos))
pokew(wordvalues + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
@ -90,6 +116,7 @@ _done
; gnomesort_pointers is not worth it over shellshort_pointers.
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
; sorts the values array (unsigned bytes).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
@ -112,6 +139,7 @@ _done
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
; sorts the values array (no-split unsigned words).
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
@ -121,13 +149,65 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
uword @zp v = peekw(values+k*$0002)
if v <= temp break
pokew(values+j*2, v)
pokew(values+j*$0002, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
pokew(values+j*$0002, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
@ -144,14 +224,14 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
cx16.r0 = peekw(pointers+k*2)
cx16.r0 = peekw(pointers+k*$0002)
void call(comparefunc)
if_cs break
pokew(pointers+j*2, cx16.r0)
pokew(pointers+j*$0002, cx16.r0)
j = k
k -= gap
}
pokew(pointers+j*2, cx16.r1)
pokew(pointers+j*$0002, cx16.r1)
}
}
}

View File

@ -151,6 +151,33 @@ _found tya
}}
}
asmsub find_eol(uword string @AY) -> ubyte @A, bool @Pc {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _notfound
cmp #13
beq _found
cmp #10
beq _found
iny
bne -
_notfound lda #255
clc
rts
_found tya
sec
rts
}}
}
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
@ -391,16 +418,18 @@ fail clc ; yes, no match found, return with c=0
}}
}
asmsub hash(str string @R0) -> ubyte @A {
asmsub hash(str string @AY) -> ubyte @A {
; experimental 8 bit hashing function.
; hash(-1)=179; clear carry; hash(i) = ROL hash(i-1) XOR string[i]
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #179
sta P8ZP_SCRATCH_REG
ldy #0
clc
- lda (cx16.r0),y
- lda (P8ZP_SCRATCH_W1),y
beq +
rol P8ZP_SCRATCH_REG
eor P8ZP_SCRATCH_REG

View File

@ -177,21 +177,17 @@ math {
}
sub randrange(ubyte n) -> ubyte {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r0H = 255 / n * n
do {
cx16.r0L = math.rnd()
} until cx16.r0L < cx16.r0H
return cx16.r0L % n
; -- return random number uniformly distributed from 0 to n-1
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rnd() * (n as uword)
return cx16.r0H
}
sub randrangew(uword n) -> uword {
; -- return random number uniformly distributed from 0 to n-1 (compensates for divisibility bias)
cx16.r1 = 65535 / n * n
do {
cx16.r0 = math.rndw()
} until cx16.r0 < cx16.r1
return cx16.r0 % n
; -- return random number uniformly distributed from 0 to n-1
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
cx16.r0 = math.rndw() * n
return math.mul16_last_upper()
}
sub rndseed(uword seed1, uword seed2) {
@ -408,25 +404,36 @@ math {
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@ -0,0 +1,103 @@
; **experimental** data sorting routines, API subject to change!!
; NOTE: gnomesort is not implemented here, just use shellshort.
sorting {
%option ignore_unused
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = values[i]
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = values[k]
if v <= temp break
if j < gap break
values[j] = v
j = k
k -= gap
}
values[j] = temp
}
}
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(values+i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
if v <= temp break
pokew(values+j*2, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
}

View File

@ -53,34 +53,47 @@ strings {
target[ix]=0
}
sub find(str st, ubyte character) -> ubyte {
sub find(str st, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte {
sub find_eol(str st) -> ubyte, bool {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix] in "\x0a\x0d" {
sys.set_carry()
return ix, true
}
}
sys.clear_carry()
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in length(stringptr)-1 downto 0 {
if stringptr[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub contains(str st, ubyte character) -> bool {
@ -179,7 +192,7 @@ strings {
sub hash(str st) -> ubyte {
; experimental 8 bit hashing function.
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
; (experimental because the quality of the resulting hash value still has to be determined)
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
ubyte hashcode = 179
ubyte ix
sys.clear_carry()

View File

@ -7,12 +7,14 @@ sys {
const ubyte target = 255 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 8
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -198,6 +200,12 @@ sys {
}}
}
sub get_as_returnaddress(uword address) -> uword {
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
address--
return mkword(lsb(address), msb(address))
}
sub pop() -> ubyte {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
@ -231,9 +239,8 @@ sys {
if_cs
cx16.r0L |= 1
; TODO: overflow flag not yet supported
; if_vs
; cx16.r0L |= %01000000
if_vs
cx16.r0L |= %01000000
return cx16.r0L
}

View File

@ -57,7 +57,7 @@ private fun compileMain(args: Array<String>): Boolean {
val plainText by cli.option(ArgType.Boolean, fullName = "plaintext", description = "output only plain text, no colors or fancy symbols")
val printAst1 by cli.option(ArgType.Boolean, fullName = "printast1", description = "print out the internal compiler AST")
val printAst2 by cli.option(ArgType.Boolean, fullName = "printast2", description = "print out the simplified AST that is used for code generation")
val quietAll by cli.option(ArgType.Boolean, fullName = "quiet", description = "don't print compiler and assembler messages")
val quietAll by cli.option(ArgType.Boolean, fullName = "quiet", description = "don't print compiler and assembler messages, except warnings and errors")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler messages")
val slabsGolden by cli.option(ArgType.Boolean, fullName = "slabsgolden", description = "put memory() slabs in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
val slabsHighBank by cli.option(ArgType.Int, fullName = "slabshigh", description = "put memory() slabs in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
@ -65,12 +65,14 @@ private fun compileMain(args: Array<String>): Boolean {
val dontSplitWordArrays by cli.option(ArgType.Boolean, fullName = "dontsplitarrays", description = "don't store any word array as split lsb/msb in memory, as if all of those have @nosplit")
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 compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of ${CompilationTargets.joinToString(",")} or a custom target properties file) (required)")
val showTimings by cli.option(ArgType.Boolean, fullName = "timings", description = "show internal compiler timings (for performance analysis)")
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "run a .p8ir IR source file in the embedded VM")
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
val version by cli.option(ArgType.Boolean, fullName = "version", description = "print compiler version and exit")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").optional().multiple(999)
try {
cli.parse(args)
@ -80,6 +82,11 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if(version==true) {
banner()
return true
}
if(quietAll!=true)
banner()
@ -123,6 +130,11 @@ private fun compileMain(args: Array<String>): Boolean {
return true
}
if(moduleFiles.isEmpty()) {
System.err.println("No module file(s) specified")
return false
}
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
return false
@ -170,6 +182,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,
@ -254,6 +267,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,

View File

@ -1,8 +1,8 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.AstException
import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.VarDecl
@ -94,16 +94,16 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
// 1 arg, type = anything, result type = ubyte or uword
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference && args[0] !is NumericLiteral)
throw SyntaxError("sizeof argument should be an identifier or number", position)
if(args[0] !is IdentifierReference && args[0] !is NumericLiteral && args[0] !is AddressOf)
throw CannotEvaluateException("sizeof","argument should be an identifier, number, or type name")
val dt = args[0].inferType(program)
if(dt.isKnown) {
if(args[0] is NumericLiteral)
if(args[0] is NumericLiteral || args[0] is AddressOf)
return NumericLiteral.optimalInteger(program.memsizer.memorySize(dt.getOrUndef(), null), position)
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
val target = (args[0] as? IdentifierReference)?.targetStatement()
?: throw SyntaxError("wrong argument type", position)
return when {
dt.isArray -> {
@ -112,19 +112,40 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt, length), position)
}
dt.isString -> throw SyntaxError("sizeof(str) is undefined, did you mean len, or perhaps strings.length?", position)
else -> NumericLiteral(BaseDataType.UBYTE, program.memsizer.memorySize(dt.getOrUndef(), null).toDouble(), position)
else -> NumericLiteral.optimalInteger( program.memsizer.memorySize(dt.getOrUndef(), null), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
val identifier = args[0] as? IdentifierReference
if(identifier?.nameInSource?.size==1) {
when(identifier.nameInSource[0]) {
"ubyte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UBYTE), position)
"byte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BYTE), position)
"uword" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UWORD), position)
"word" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.WORD), position)
"long" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.LONG), position)
"float" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.FLOAT), position)
"bool" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BOOL), position)
}
}
// the argument could refer to a struct declaration
val struct = (args[0] as? IdentifierReference)?.targetStructDecl()
if(struct!=null) {
val size = struct.memsize(program.memsizer)
return NumericLiteral(BaseDataType.UBYTE, size.toDouble(), position)
}
throw SyntaxError("sizeof argument should be an identifier, number, or type name", position)
}
}
@Suppress("unused")
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl()
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteral.optimalInteger(arraySize, position)
@ -134,7 +155,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteral.optimalInteger((args[0] as StringLiteral).value.length, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
val target = (args[0] as IdentifierReference).targetVarDecl()
?: throw CannotEvaluateException("len", "no target vardecl")
return when {

View File

@ -24,9 +24,11 @@ import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.nameWithoutExtension
import kotlin.math.round
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.measureTime
import kotlin.time.measureTimedValue
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
@ -40,6 +42,7 @@ class CompilerArguments(val filepath: Path,
val warnSymbolShadowing: Boolean,
val quietAll: Boolean,
val quietAssembler: Boolean,
val showTimings: Boolean,
val asmListfile: Boolean,
val includeSourcelines: Boolean,
val experimentalCodegen: Boolean,
@ -83,9 +86,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
try {
val totalTime = measureTimeMillis {
val totalTime = measureTime {
val libraryDirs = if(compTarget.libraryPath!=null) listOf(compTarget.libraryPath.toString()) else emptyList()
val (program, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs, libraryDirs, args.quietAll)
val (parseresult, parseDuration) = measureTimedValue {
parseMainModule(
args.filepath,
args.errors,
compTarget,
args.sourceDirs,
libraryDirs,
args.quietAll
)
}
val (program, options, imported) = parseresult
compilationOptions = options
with(compilationOptions) {
@ -120,87 +134,119 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
processAst(program, args.errors, compilationOptions)
val processDuration = measureTime {
processAst(program, args.errors, compilationOptions)
}
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
val optimizeDuration = measureTime {
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
}
}
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
val postprocessDuration = measureTime {
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
}
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
var createAssemblyDuration = Duration.ZERO
var simplifiedAstDuration = Duration.ZERO
if (args.writeAssembly) {
// re-initialize memory areas with final compilationOptions
compilationOptions.compTarget.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
postprocessSimplifiedAst(intermediateAst, symbolTable, args.errors)
postprocessSimplifiedAst(intermediateAst, symbolTable, compilationOptions, args.errors)
args.errors.report()
if(compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if (args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
intermediateAst
}
simplifiedAstDuration =simplifiedAstDuration2
if(args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions, program.generatedLabelSequenceNumber)) {
System.err.println("Error in codegeneration or assembler")
return null
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
intermediateAst,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
)
) {
System.err.println("Error in codegeneration or assembler")
return null
}
}
ast = intermediateAst
} else {
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
if(args.printAst2) {
if (args.printAst2) {
System.err.println("There is no simplified Ast available if assembly generation is disabled.")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll && args.showTimings) {
println("\n**** TIMINGS ****")
println("source parsing : ${parseDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast processing : ${processDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast optimizing : ${optimizeDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast postprocess : ${postprocessDuration.toString(DurationUnit.SECONDS, 3)}")
println("code prepare : ${simplifiedAstDuration.toString(DurationUnit.SECONDS, 3)}")
println("code generation : ${createAssemblyDuration.toString(DurationUnit.SECONDS, 3)}")
val totalDuration = parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration
println(" total : ${totalDuration.toString(DurationUnit.SECONDS, 3)}")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll) {
val seconds = totalTime / 1000.0
println("\nTotal compilation+assemble time: ${round(seconds * 100.0) / 100.0} sec.")
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
}
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
} catch (px: ParseError) {
args.errors.print_single_error("${px.position.toClickableStr()} parse error: ${px.message}".trim())
args.errors.printSingleError("${px.position.toClickableStr()} parse error: ${px.message}".trim())
} catch (ac: ErrorsReportedException) {
if(args.printAst1 && resultingProgram!=null) {
println("\n*********** COMPILER AST *************")
@ -217,17 +263,17 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
}
if(!ac.message.isNullOrEmpty()) {
args.errors.print_single_error(ac.message!!)
args.errors.printSingleError(ac.message!!)
}
} catch (nsf: NoSuchFileException) {
args.errors.print_single_error("File not found: ${nsf.message}")
args.errors.printSingleError("File not found: ${nsf.message}")
} catch (ax: AstException) {
args.errors.print_single_error(ax.toString())
args.errors.printSingleError(ax.toString())
} catch (x: Exception) {
args.errors.print_single_error("\ninternal error")
args.errors.printSingleError("\ninternal error")
throw x
} catch (x: NotImplementedError) {
args.errors.print_single_error("\ninternal error: missing feature/code")
args.errors.printSingleError("\ninternal error: missing feature/code")
throw x
}
@ -410,7 +456,6 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.preprocessAst(errors, compilerOptions)
if(errors.noErrors() && compilerOptions.dumpSymbols) {
printSymbols(program)
exitProcess(0)
@ -424,7 +469,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@ -486,7 +531,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)
@ -495,8 +540,21 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
program.verifyFunctionArgTypes(errors, compilerOptions)
errors.report()
program.moveMainBlockAsFirst(compilerOptions.compTarget)
val fixer = BeforeAsmAstChanger(program, compilerOptions, errors)
fixer.visit(program)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(program)
}
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val cleaner = BeforeAsmTypecastCleaner(program, errors)
cleaner.visit(program)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(program)
}
}
private fun createAssemblyAndAssemble(program: PtProgram,

View File

@ -95,7 +95,7 @@ internal class ErrorReporter(val colors: IConsoleColors): IErrorReporter {
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
override fun noErrorForLine(position: Position) = !messages.any { it.position.line==position.line && it.severity!=MessageSeverity.INFO }
override fun print_single_error(errormessage: String) {
override fun printSingleError(errormessage: String) {
System.out.flush()
colors.error(System.err)
System.err.println(errormessage)

View File

@ -39,7 +39,7 @@ class ModuleImporter(private val program: Program,
println("Compiling program ${Path("").absolute().relativize(programPath)}")
println("Compiler target: $compilationTargetName")
}
val source = ImportFileSystem.getFile(programPath, false)
val source = ImportFileSystem.getFile(programPath)
return Ok(importModule(source))
}
}
@ -158,7 +158,7 @@ class ModuleImporter(private val program: Program,
normalLocations.forEach {
try {
return Ok(ImportFileSystem.getFile(it.resolve(fileName), false))
return Ok(ImportFileSystem.getFile(it.resolve(fileName)))
} catch (_: NoSuchFileException) {
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.StringLiteral
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@ -24,19 +20,6 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(this)
}
val cleaner = BeforeAsmTypecastCleaner(this, errors)
cleaner.visit(this)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(this)
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
@ -107,8 +90,8 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options.compTarget, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)
@ -136,7 +119,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
checker2.visit(this)
if(errors.noErrors()) {
val lit2decl = LiteralsToAutoVars(this, errors)
val lit2decl = LiteralsToAutoVarsAndRecombineIdentifiers(this, errors)
lit2decl.visit(this)
while(errors.noErrors() && lit2decl.applyModifications()>0)
lit2decl.visit(this)
@ -185,8 +168,8 @@ internal fun Program.moveMainBlockAsFirst(target: ICompilationTarget) {
}
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program)
internal fun IdentifierReference.isSubroutineParameter(): Boolean {
val vardecl = this.targetVarDecl()
if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
@ -219,7 +202,7 @@ internal fun Subroutine.hasRtsInAsm(checkOnlyLastInstruction: Boolean): Boolean
}
internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, statement: Statement, errors: IErrorReporter): Statement? {
when (val targetStatement = this.targetStatement(program)) {
when (val targetStatement = targetStatement(program.builtinFunctions)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
is VarDecl -> {
if(statement is Jump) {
@ -231,7 +214,7 @@ internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, st
else
errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
}
is Alias -> {
is Alias, is StructDecl, is StructFieldRef -> {
return targetStatement
}
null -> {

View File

@ -40,7 +40,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(alias: Alias) {
if(alias.target.targetStatement(program)==null)
if(alias.target.targetStatement(program.builtinFunctions)==null)
errors.err("undefined symbol: ${alias.target.nameInSource.joinToString(".") }", alias.target.position)
}
@ -94,7 +94,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
super.visit(decl)
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
@ -169,13 +168,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
private fun visitFunctionCall(call: IFunctionCall) {
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
val target = call.target.targetStatement(program)
val target = call.target.targetStatement(program.builtinFunctions)
if(target==null) {
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
return
}
}
when (val target = call.target.targetStatement(program)) {
when (val target = call.target.targetStatement(program.builtinFunctions)) {
is Subroutine -> {
val expectedNumberOfArgs: Int = target.parameters.size
if(call.args.size != expectedNumberOfArgs) {
@ -215,7 +214,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(target.type!=VarDeclType.VAR || !target.datatype.isUnsignedWord)
errors.err("wrong address variable datatype, expected uword", call.target.position)
}
is Alias -> {}
is Alias, is StructDecl, is StructFieldRef -> {}
null -> {}
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
}

View File

@ -1,12 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.findParentNode
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@ -129,7 +124,14 @@ class AstPreprocessor(val program: Program,
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
if(decl.value!=null) {
decl.names.forEach { name ->
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, null, false, decl.position)
val target = AssignTarget(
IdentifierReference(listOf(name), decl.position),
null,
null,
null,
false,
position = decl.position
)
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.InsertAfter(decl, assign, scope))
}
@ -145,7 +147,14 @@ class AstPreprocessor(val program: Program,
} else {
// handle declaration of a single variable
if(decl.value!=null && decl.datatype.isNumericOrBool) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, false, decl.position)
val target = AssignTarget(
IdentifierReference(listOf(decl.name), decl.position),
null,
null,
null,
false,
position = decl.position
)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
@ -181,7 +190,7 @@ class AstPreprocessor(val program: Program,
val nextAssignment = decl.nextSibling() as? Assignment
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
// check if the following assignment initializes the variable
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl()===decl) {
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
nextAssignment.origin = AssignmentOrigin.VARINIT
}
@ -191,6 +200,29 @@ class AstPreprocessor(val program: Program,
return makeUnSplitArray(decl)
}
// convert all antlr names to structs
val antlrTypeName = decl.datatype.subTypeFromAntlr
if(antlrTypeName!=null) {
val node = decl.definingScope.lookup(antlrTypeName)
if(node==null) {
errors.err("cannot find struct type ${antlrTypeName.joinToString(".")}", decl.position)
} else {
decl.datatype.setActualSubType(node as StructDecl)
}
}
return noModifications
}
override fun after(struct: StructDecl, parent: Node): Iterable<IAstModification> {
// convert all antlr names to structs
struct.fields.forEach {
if(it.first.subTypeFromAntlr!=null) {
val struct = struct.definingScope.lookup(it.first.subTypeFromAntlr!!) as? ISubType
if(struct!=null)
it.first.setActualSubType(struct)
}
}
return noModifications
}
@ -241,7 +273,23 @@ class AstPreprocessor(val program: Program,
errors.err("can only use R0-R15 as register param for normal subroutines", it.position)
}
}
return mods
if(mods.isNotEmpty())
return mods
}
}
subroutine.returntypes.forEach {
if(it.subTypeFromAntlr!=null) {
val struct = subroutine.lookup(it.subTypeFromAntlr!!) as? ISubType
if(struct!=null)
it.setActualSubType(struct)
}
}
subroutine.parameters.forEach {
if(it.type.subTypeFromAntlr!=null) {
val struct = subroutine.lookup(it.type.subTypeFromAntlr!!) as? ISubType
if(struct!=null)
it.type.setActualSubType(struct)
}
}
@ -253,6 +301,19 @@ class AstPreprocessor(val program: Program,
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
checkStringParam(functionCallExpr as IFunctionCall, stmtOfExpression)
if(functionCallExpr.target.nameInSource==listOf("sizeof")) {
val arg = functionCallExpr.args.firstOrNull()
if (arg is PtrDereference) {
// replace sizeof(ptr^^) with sizeof(type that ptr points to)
val dt = arg.inferType(program)
if(dt.isKnown) {
val dtName = dt.getOrUndef().toString()
val newArg = IdentifierReference(dtName.split("."), arg.position)
return listOf(IAstModification.ReplaceNode(arg, newArg, functionCallExpr))
}
}
}
return noModifications
}
@ -262,13 +323,34 @@ class AstPreprocessor(val program: Program,
}
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
val tgt = alias.target.targetStatement(program)
val tgt = alias.target.targetStatement(program.builtinFunctions)
if(tgt is Block) {
errors.err("cannot alias blocks", alias.target.position)
}
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// convert all antlr names to structs
if(typecast.type.subTypeFromAntlr!=null) {
val struct = typecast.definingScope.lookup(typecast.type.subTypeFromAntlr!!) as? ISubType
if(struct!=null)
typecast.type.setActualSubType(struct)
}
return noModifications
}
override fun after(field: StructFieldRef, parent: Node): Iterable<IAstModification> {
if(field.type.subTypeFromAntlr!=null) {
val struct = field.definingScope.lookup(field.type.subTypeFromAntlr!!) as? ISubType
if(struct!=null)
field.type.setActualSubType(struct)
}
return noModifications
}
private fun checkStringParam(call: IFunctionCall, stmt: Statement) {
val targetStatement = call.target.checkFunctionOrLabelExists(program, stmt, errors)
if(targetStatement!=null) {

View File

@ -3,6 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.defaultZero
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
@ -48,18 +49,27 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
val mods = mutableListOf<IAstModification>()
// 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 if an assembly block doesn't contain a rts/rti. AND if there's no return value(s) because we can't make one up!
if (!subroutine.isAsmSubroutine) {
if(subroutine.isEmpty()) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty())
errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty()) {
// .... we cannot return this as an error, because that also breaks legitimate cases where the return is done from within a nested scope somewhere
// errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
} else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
}
}
@ -76,8 +86,20 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
&& prevStmt !is Subroutine
&& prevStmt !is Return
) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
if(!subroutine.inline) {
if(outerScope is Subroutine && outerScope.returntypes.isNotEmpty()) {
if(outerScope.returntypes.size>1 || !outerScope.returntypes[0].isNumericOrBool) {
errors.err("subroutine is missing a return statement to avoid falling through into nested subroutine", outerStatements[subroutineStmtIdx-1].position)
} else {
val zero = defaultZero(outerScope.returntypes[0].base, Position.DUMMY)
val returnStmt = Return(arrayOf(zero), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
} else {
val returnStmt = Return(arrayOf(), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
}
}
}

View File

@ -9,7 +9,6 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.IErrorReporter
import prog8.code.core.isByte
import prog8.code.core.isWord
@ -29,14 +28,14 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
}
}
if(typecast.type==sourceDt.base)
if(typecast.type==sourceDt)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(sourceDt.isPassByRef) {
if(typecast.type == BaseDataType.UWORD) {
if(typecast.type.isUnsignedWord) {
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
return if(identifier.isSubroutineParameter()) {
listOf(
IAstModification.ReplaceNode(
typecast,
@ -48,7 +47,7 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
listOf(
IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, null, false, typecast.position),
AddressOf(identifier, null, null, false, typecast.position),
parent
)
)

View File

@ -5,13 +5,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.ComparisonOperators
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val target: ICompilationTarget, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@ -27,11 +24,24 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments
// - remove alias nodes
// - convert on..goto/call to jumpaddr array and separate goto/call
// - replace implicit pointer dereference chains (a.b.c.d) with explicit ones (a^^.b^^.c^^.d)
// - replace ptr^^ by @(ptr) if ptr is just an uword.
// - replace p1^^ = p2^^ by memcopy.
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.datatype.isPointer && decl.datatype.sub==BaseDataType.STR) {
errors.info("^^str replaced by ^^ubyte", decl.position)
val decl2 = decl.copy(DataType.pointer(BaseDataType.UBYTE))
return listOf(IAstModification.ReplaceNode(decl, decl2, parent))
}
return noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
val label = program.makeLabel("after", breakStmt.position)
@ -106,7 +116,7 @@ if not CONDITION
untilLoop.body,
IfElse(invertCondition(untilLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos)
), pos)
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
@ -114,10 +124,6 @@ if not CONDITION
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
if(!whileLoop.condition.inferType(program).isBool) {
errors.err("condition should be a boolean", whileLoop.condition.position)
}
/*
while true -> repeat
while false -> discard
@ -150,7 +156,7 @@ _after:
loopLabel,
IfElse(invertCondition(whileLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos),
whileLoop.body,
program.jumpLabel(loopLabel),
@ -173,7 +179,14 @@ _after:
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), null, false, position)
val tgt = AssignTarget(
null,
null,
DirectMemoryWrite(functionCall.args[0], position),
null,
false,
position = position
)
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
@ -204,21 +217,56 @@ _after:
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
if(arrayIndexedExpression.pointerderef!=null) {
return noModifications
}
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype.isUnsignedWord) {
val wordIndex = TypecastExpression(indexExpr, BaseDataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", wordIndex, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(arrayVar!=null && (arrayVar.datatype.isUnsignedWord || (arrayVar.datatype.isPointer && arrayVar.datatype.sub==BaseDataType.UBYTE))) {
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(
arrayIndexedExpression.plainarrayvar!!.copy(),
"+",
wordIndex,
arrayIndexedExpression.position
)
return if (parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, null, false, arrayIndexedExpression.position)
val newtarget = AssignTarget(
null,
null,
memwrite,
null,
false,
position = arrayIndexedExpression.position
)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(address, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
} else if(arrayVar!=null && (arrayVar.type==VarDeclType.MEMORY || arrayVar.datatype.isString || arrayVar.datatype.isPointer || arrayVar.datatype.isArray)) {
return noModifications
} else if(arrayVar!=null) {
// it could be a pointer dereference instead of a simple array variable
TODO("deref[word] rewrite ???? $arrayIndexedExpression")
// val dt = arrayIndexedExpression.plainarrayvar!!.traverseDerefChainForDt(null)
// if(dt.isUnsignedWord) {
// // ptr.field[index] --> @(ptr.field + index)
// val index = arrayIndexedExpression.indexer.indexExpr
// val address = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", index, arrayIndexedExpression.position)
// if(parent is AssignTarget) {
// val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
// return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memwrite, parent))
// } else {
// val memread = DirectMemoryRead(address, arrayIndexedExpression.position)
// return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
// }
// }
}
return noModifications
}
@ -259,6 +307,63 @@ _after:
return listOf(IAstModification.ReplaceNode(expr, squareCall, parent))
}
if(expr.operator==".") {
val left = expr.left as? ArrayIndexedExpression
val right = expr.right as? PtrDereference
if(left!=null && right!=null) {
if(parent is BinaryExpression && parent.operator=="." && parent.right===expr) {
val parentLeft = parent.left as? IdentifierReference
if(parentLeft!=null) {
// parent is:
// BinaryExpression "."
// / \
// IdRef (this BinExpr)
// x.y / \
// ArrayIdx PtrDeref
// z[i] field
//
// transform this into this so it can be processed further:
//
// BinaryExpression "."
// / \
// ArrayIdx IdentifierRef
// x.y.z[i] field
val combinedIdentifier = IdentifierReference(parentLeft.nameInSource+left.plainarrayvar!!.nameInSource, parentLeft.position)
val newleft = ArrayIndexedExpression(combinedIdentifier, null, left.indexer, left.position)
val newright = IdentifierReference(listOf(right.chain.single()), right.position)
return listOf(
IAstModification.ReplaceNode(parent.left, newleft, parent),
IAstModification.ReplaceNode(parent.right, newright, parent)
)
}
}
}
if(expr.left is ArrayIndexedExpression && right!=null) {
// replace replace x.y.listarray[2]^^.value with just x.y.listarray[2] . value
// this will be further modified elsewhere
val ident = IdentifierReference(right.chain, right.position)
return listOf(IAstModification.ReplaceNode(expr.right, ident, expr))
// TODO hmmm , replace cx16.r1 = listarray[2]^^.value with a temp pointer var to contain the indexed value
// val assign = expr.parent as? Assignment
// if(assign!=null) {
// val ptrDt = expr.left.inferType(program).getOrUndef()
// val pointerVar = VarDecl.createAuto(ptrDt)
// val pointerIdent = IdentifierReference(pointerVar.name.split("."), expr.position)
// val tgt = AssignTarget(pointerIdent, null, null, null, false, null, position = expr.position)
// val assignPtr = Assignment(tgt, expr.left, AssignmentOrigin.USERCODE, expr.position)
// val derefValue = PtrDereference(pointerIdent.nameInSource + right.chain, false, expr.position)
// return listOf(
// IAstModification.InsertBefore(assign, assignPtr, assign.parent as IStatementContainer),
// IAstModification.ReplaceNode(assign.value, derefValue, assign),
// IAstModification.InsertFirst(pointerVar, expr.definingSubroutine!!)
// )
// }
}
}
return noModifications
}
@ -270,8 +375,8 @@ _after:
val addrOf = memread.addressExpression as? AddressOf
if(addrOf?.arrayIndex!=null)
return noModifications
if(addrOf!=null && addrOf.identifier.inferType(program).isWords) {
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), memread.position), mutableListOf(addrOf.identifier), memread.position)
if(addrOf!=null && addrOf.identifier?.inferType(program)?.isWords==true) {
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), memread.position), mutableListOf(addrOf.identifier!!), memread.position)
return listOf(IAstModification.ReplaceNode(memread, lsb, parent))
}
val expr = memread.addressExpression as? BinaryExpression
@ -279,9 +384,11 @@ _after:
val addressOf = expr.left as? AddressOf
val offset = (expr.right as? NumericLiteral)?.number?.toInt()
if(addressOf!=null && offset==1) {
val variable = addressOf.identifier.targetVarDecl(program)
val variable = addressOf.identifier?.targetVarDecl()
if(variable!=null && variable.datatype.isWord) {
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(
addressOf.identifier!!
), memread.position)
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
}
}
@ -350,4 +457,195 @@ _after:
return noModifications
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
if (identifier.nameInSource.size>1) {
val firstTarget = (identifier.firstTarget() as? VarDecl)
val firstDt = firstTarget?.datatype
if (firstDt?.isPointer == true) {
// the a.b.c.d can be a pointer dereference chain ending in a struct field; a^^.b^^.c^^.d
val chain = mutableListOf(identifier.nameInSource[0])
var struct = firstDt.subType as? StructDecl
for(name in identifier.nameInSource.drop(1)) {
if(struct==null) {
errors.err("unknown field '${name}", position = identifier.position)
return noModifications
}
val fieldDt = struct.getFieldType(name)
if(fieldDt==null) {
errors.err("unknown field '${name}' in struct '${struct.name}'", identifier.position)
return noModifications
}
if(fieldDt.isPointer) {
chain.add(name)
struct = fieldDt.subType as? StructDecl
} else {
chain.add(name)
struct = null
}
}
val deref = PtrDereference(chain, false, identifier.position)
return listOf(IAstModification.ReplaceNode(identifier, deref, parent))
}
}
return noModifications
}
override fun after(ongoto: OnGoto, parent: Node): Iterable<IAstModification> {
val indexDt = ongoto.index.inferType(program).getOrUndef()
if(!indexDt.isUnsignedByte)
return noModifications
val numlabels = ongoto.labels.size
val split = if(ongoto.isCall)
true // for calls (indirect JSR), split array is always the optimal choice
else
target.cpu==CpuType.CPU6502 // for goto (indirect JMP), split array is optimal for 6502, but NOT for the 65C02 (it has a different JMP addressing mode available)
val arrayDt = DataType.arrayFor(BaseDataType.UWORD, split)
val labelArray = ArrayLiteral(InferredTypes.knownFor(arrayDt), ongoto.labels.toTypedArray(), ongoto.position)
val jumplistArray = VarDecl.createAutoOptionalSplit(labelArray)
val indexValue: Expression
val conditionVar: VarDecl?
val assignIndex: Assignment?
// put condition in temp var, if it is not simple; to avoid evaluating expression multiple times
if (ongoto.index.isSimple) {
indexValue = ongoto.index
assignIndex = null
conditionVar = null
} else {
conditionVar = VarDecl.createAuto(indexDt)
indexValue = IdentifierReference(listOf(conditionVar.name), conditionVar.position)
val varTarget = AssignTarget(indexValue, null, null, null, false, position=conditionVar.position)
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
}
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), null, ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
val callIndexed = AnonymousScope.empty(ongoto.position)
if(ongoto.isCall) {
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
} else {
callIndexed.statements.add(Jump(callTarget, ongoto.position))
}
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
// if index<numlabels call(labels[index])
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope.empty(), ongoto.position)
} else {
// if index>=numlabels elselabel() else call(labels[index])
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, ongoto.elsepart!!, callIndexed, ongoto.position)
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
}
override fun after(deref: PtrDereference, parent: Node): Iterable<IAstModification> {
val isLHS = parent is AssignTarget
val varDt = (deref.firstTarget() as? VarDecl)?.datatype
if(varDt?.isUnsignedWord==true || (varDt?.isPointer==true && varDt.sub?.isByte==true)) {
// replace ptr^^ by @(ptr) when ptr is uword or ^^byte
val identifier = IdentifierReference(deref.chain, deref.position)
if(isLHS && varDt.sub==BaseDataType.UBYTE) {
val memwrite = DirectMemoryWrite(identifier, deref.position)
return listOf(IAstModification.ReplaceNode(deref, memwrite, parent))
} else if(!isLHS) {
val memread = DirectMemoryRead(identifier, deref.position)
val replacement = if (varDt.sub == BaseDataType.BYTE)
TypecastExpression(memread, DataType.BYTE, true, memread.position)
else
memread
return listOf(IAstModification.ReplaceNode(deref, replacement, parent))
}
}
val expr = deref.parent as? BinaryExpression
if (expr != null && expr.operator == ".") {
if (expr.left is IdentifierReference && expr.right === deref) {
// replace (a) . (b^^) by (a.b)^^
val name = (expr.left as IdentifierReference).nameInSource + deref.chain
val replacement = PtrDereference(name, deref.derefLast, deref.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, expr.parent))
} else if(expr.left===deref && expr.right is ArrayIndexedExpression) {
// replace (a^^) . ( s[b] ) by (a^^.s^^)[b]
val idx = expr.right as ArrayIndexedExpression
if(idx.plainarrayvar!=null) {
val name = deref.chain + idx.plainarrayvar!!.nameInSource
val ptrDeref = PtrDereference(name, false, deref.position)
val indexer = ArrayIndexedExpression(null, ptrDeref, idx.indexer, idx.position)
return listOf(IAstModification.ReplaceNode(expr, indexer, expr.parent))
} else {
TODO("convert ptr.p[idx]")
}
}
}
return noModifications
}
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
// get rid of the ArrayIndexedPtrDereference AST node, replace it with other AST nodes that are equivalent
// z[i]^^.field --> (z[i]) . (field)
val firstIndexed = deref.chain.indexOfFirst { it.second!=null }
require(firstIndexed==0) {"array dereference expected on first element in the chain, anything before it should be part of the parent binexpression"}
val index = deref.chain.first()
val tail = deref.chain.drop(1)
if(tail.any { it.second!=null }) {
TODO("support multiple array indexed dereferencings ${deref.position}")
} else {
val pointer = IdentifierReference(listOf(index.first), deref.position)
val left = ArrayIndexedExpression(pointer, null, index.second!!, deref.position)
val right = PtrDereference(tail.map { it.first }, deref.derefLast, deref.position)
val derefExpr = BinaryExpression(left, ".", right, deref.position)
return listOf(IAstModification.ReplaceNode(deref, derefExpr, parent))
}
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutine.returntypes.withIndex().forEach { (idx, rt) ->
if(rt.isPointer && rt.sub==BaseDataType.STR) {
errors.info("^^str replaced by ^^ubyte in return type(s)", subroutine.position)
subroutine.returntypes[idx] = DataType.pointer(BaseDataType.UBYTE)
}
}
subroutine.parameters.withIndex().forEach { (idx, param) ->
if(param.type.isPointer && param.type.sub==BaseDataType.STR) {
errors.info("^^str replaced by ^^ubyte", param.position)
subroutine.parameters[idx] = SubroutineParameter(param.name, DataType.pointer(BaseDataType.UBYTE), param.zp, param.registerOrPair, param.position)
}
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val targetDt = assignment.target.inferType(program)
val sourceDt = assignment.value.inferType(program)
if(targetDt.isStructInstance && sourceDt.isStructInstance) {
if(targetDt == sourceDt) {
val size = program.memsizer.memorySize(sourceDt.getOrUndef(), null)
require(program.memsizer.memorySize(targetDt.getOrUndef(), null)==size)
val sourcePtr = IdentifierReference((assignment.value as PtrDereference).chain, assignment.position)
val targetPtr = IdentifierReference(assignment.target.pointerDereference!!.chain, assignment.position)
val numBytes = NumericLiteral.optimalInteger(size, assignment.position)
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assignment.position),
mutableListOf(sourcePtr, targetPtr, numBytes),
false, assignment.position)
return listOf(IAstModification.ReplaceNode(assignment, memcopy, parent))
}
}
return noModifications
}
}

View File

@ -1,18 +1,15 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.ast.PtContainmentCheck
import prog8.code.core.*
import prog8.code.core.IErrorReporter
internal class LiteralsToAutoVars(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
@ -83,14 +80,28 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.names.size>1) {
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine(program)
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine()
if(fcallTarget!=null) {
// ubyte a,b,c = multi() --> ubyte a,b,c / a,b,c = multi()
val modifications = mutableListOf<IAstModification>()
val variables = decl.names.map {
AssignTarget(IdentifierReference(listOf(it), decl.position), null, null, null, false, decl.position)
AssignTarget(
IdentifierReference(listOf(it), decl.position),
null,
null,
null,
false,
position = decl.position
)
}
val multiassignFuncCall = Assignment(AssignTarget(null, null, null, variables, false, decl.position), decl.value!!, AssignmentOrigin.VARINIT, decl.position)
val multiassignFuncCall = Assignment(AssignTarget(
null,
null,
null,
variables,
false,
position = decl.position
), decl.value!!, AssignmentOrigin.VARINIT, decl.position)
decl.value = null
modifications += IAstModification.InsertAfter(decl, multiassignFuncCall, parent as IStatementContainer)
return modifications
@ -98,8 +109,8 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
// note: the desugaring of a multi-variable vardecl has to be done here
// and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise)
if(!decl.datatype.isNumericOrBool)
errors.err("can only multi declare numeric and boolean variables", decl.position)
if(!decl.datatype.isNumericOrBool && !decl.datatype.isPointer && !decl.datatype.isStructInstance)
errors.err("cannot multi declare variables of this type", decl.position)
if(decl.alignment != 0u) {
errors.err("only single variable declarations can have alignment", decl.position)
return noModifications
@ -116,7 +127,7 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
}
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
val target = alias.target.targetStatement(program)
val target = alias.target.targetStatement()
if(target is Alias) {
val newAlias = Alias(alias.alias, target.target, alias.position)
return listOf(IAstModification.ReplaceNode(alias, newAlias, parent))
@ -125,11 +136,11 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val target = identifier.targetStatement(program)
val target = identifier.targetStatement()
// don't replace an identifier in an Alias or when the alias points to another alias (that will be resolved first elsewhere)
if(target is Alias && parent !is Alias) {
if(target.target.targetStatement(program) !is Alias)
if(target.target.targetStatement() !is Alias)
return listOf(IAstModification.ReplaceNode(identifier, target.target.copy(position = identifier.position), parent))
}
@ -146,4 +157,26 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
// }
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".") {
val leftIdent = expr.left as? IdentifierReference
val rightIndex = expr.right as? ArrayIndexedExpression
if (leftIdent != null && rightIndex != null) {
// maybe recombine IDENTIFIER . ARRAY[IDX] --> COMBINEDIDENTIFIER[IDX]
val leftTarget = leftIdent.targetStatement()
if(leftTarget==null || leftTarget !is StructDecl) {
if(rightIndex.plainarrayvar!=null) {
val combinedName = leftIdent.nameInSource + rightIndex.plainarrayvar!!.nameInSource
val combined = IdentifierReference(combinedName, leftIdent.position)
val indexer = ArrayIndexedExpression(combined, null, rightIndex.indexer, leftIdent.position)
return listOf(IAstModification.ReplaceNode(expr, indexer, parent))
} else {
throw FatalAstException("didn't expect pointer[idx] in this phase already")
}
}
}
}
return noModifications
}
}

View File

@ -74,8 +74,11 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
is UntilLoop -> throw FatalAstException("until loops must have been converted to jumps")
is VarDecl -> transform(statement)
is StructDecl -> transform(statement)
is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps")
is OnGoto -> throw FatalAstException("ongoto must have been converted to array and separate call/goto")
is StructFieldRef -> throw FatalAstException("should not occur as part of the actual AST")
}
}
@ -96,9 +99,36 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
is StringLiteral -> transform(expr)
is TypecastExpression -> transform(expr)
is IfExpression -> transform(expr)
is PtrDereference -> transform(expr)
is ArrayIndexedPtrDereference -> throw FatalAstException("this should have been converted to some other ast nodes")
}
}
private fun transform(deref: PtrDereference): PtPointerDeref {
val type = deref.inferType(program).getOrElse {
throw FatalAstException("unknown dt")
}
val startpointer: PtIdentifier
val chain: List<String>
var targetVar = deref.definingScope.lookup(deref.chain) as? VarDecl
if(targetVar!=null) {
startpointer = PtIdentifier(targetVar.scopedName.joinToString("."), targetVar.datatype, deref.position)
chain = emptyList()
} else {
targetVar = deref.definingScope.lookup(deref.chain.take(1)) as? VarDecl
if(targetVar!=null) {
startpointer = PtIdentifier(targetVar.scopedName.joinToString("."), targetVar.datatype, deref.position)
chain = deref.chain.drop(1)
} else {
TODO("find startpointer from ${deref.chain} ${deref.position}")
}
}
val result = PtPointerDeref(type, chain, deref.derefLast, deref.position)
result.add(startpointer)
return result
}
private fun transform(ifExpr: IfExpression): PtIfExpression {
val type = ifExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val ifexpr = PtIfExpression(type, ifExpr.position)
@ -157,6 +187,38 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
else -> Pair("", null)
}
if(augmentedValue!=null) {
if(srcAssign.target.inferType(program).isPointer) {
val expr = srcExpr as BinaryExpression
if(expr.operator=="+") {
// pointer arithmetic: add the size of the struct times the argument
val leftDt = expr.left.inferType(program).getOrUndef()
require(leftDt.isPointer && !expr.right.inferType(program).isPointer)
val structSize = leftDt.size(program.memsizer)
val constValue = augmentedValue.constValue(program)
if(constValue!=null) {
val total = constValue.number*structSize
if (total == 0.0)
return PtNop(srcAssign.position)
else {
val assign = PtAugmentedAssign(operator, srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(PtNumber(BaseDataType.UWORD, total, srcAssign.position))
return assign
}
} else {
val multiplication = PtBinaryExpression("*", DataType.UWORD, srcAssign.position)
multiplication.add(transformExpression(augmentedValue))
multiplication.add(PtNumber(BaseDataType.UWORD, structSize.toDouble(), srcAssign.position))
val assign = PtAugmentedAssign(operator, srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(multiplication)
return assign
}
} else
throw FatalAstException("unexpected augmented assignment operator on pointer ${expr.operator}")
}
val assign = PtAugmentedAssign(operator, srcAssign.position)
assign.add(transform(srcAssign.target))
assign.add(transformExpression(augmentedValue))
@ -164,6 +226,9 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
if(srcAssign.origin == AssignmentOrigin.VARINIT && srcAssign.parent is Block && srcAssign.value.constValue(program)?.number==0.0)
throw FatalAstException("should not have a redundant block-level variable=0 assignment; it will be zeroed as part of BSS clear")
val assign = PtAssignment(srcAssign.position, srcAssign.origin==AssignmentOrigin.VARINIT)
val multi = srcAssign.target.multi
if(multi==null) {
@ -177,18 +242,17 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcTarget: AssignTarget): PtAssignTarget {
val target = PtAssignTarget(srcTarget.void, srcTarget.position)
if(srcTarget.identifier!=null)
target.add(transform(srcTarget.identifier!!))
else if(srcTarget.arrayindexed!=null)
target.add(transform(srcTarget.arrayindexed!!))
else if(srcTarget.memoryAddress!=null)
target.add(transform(srcTarget.memoryAddress!!))
else if(!srcTarget.void)
throw FatalAstException("invalid AssignTarget")
when {
srcTarget.identifier!=null -> target.add(transform(srcTarget.identifier!!))
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
srcTarget.pointerDereference !=null -> target.add(transform(srcTarget.pointerDereference!!))
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
}
return target
}
private fun transform(identifier: IdentifierReference): PtIdentifier {
private fun transform(identifier: IdentifierReference): PtExpression {
val (target, type) = identifier.targetNameAndType(program)
return PtIdentifier(target, type, identifier.position)
}
@ -336,9 +400,18 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
val (target, _) = srcCall.target.targetNameAndType(program)
val iType = srcCall.inferType(program)
val call = PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
val targetStruct = srcCall.target.targetStructDecl()
val call =
if(targetStruct!=null) {
// a call to a struct yields a pointer to a struct instance and means: allocate a statically initialized struct instance of that type
PtBuiltinFunctionCall("structalloc", false, true, DataType.pointer(targetStruct), srcCall.position)
} else {
// regular function call
val (target, _) = srcCall.target.targetNameAndType(program)
val iType = srcCall.inferType(program)
PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
}
for (arg in srcCall.args)
call.add(transformExpression(arg))
return call
@ -417,7 +490,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
if(binexpr.operator=="==" || binexpr.operator=="!=") {
val fcall = binexpr.left as? FunctionCallExpression
if(fcall!=null) {
val returnRegs = fcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = fcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if(returnRegs!=null && returnRegs.size==1 && returnRegs[0].statusflag!=null) {
return codeForStatusflag(fcall, returnRegs[0].statusflag!!, binexpr.operator == "==")
}
@ -426,7 +499,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
} else {
val fcall = srcIf.condition as? FunctionCallExpression
if (fcall != null) {
val returnRegs = fcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = fcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if(returnRegs!=null && returnRegs.size==1 && returnRegs[0].statusflag!=null) {
return codeForStatusflag(fcall, returnRegs[0].statusflag!!, false)
}
@ -436,7 +509,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
if(prefix!=null && prefix.operator=="not") {
val prefixedFcall = prefix.expression as? FunctionCallExpression
if (prefixedFcall != null) {
val returnRegs = prefixedFcall.target.targetSubroutine(program)?.asmReturnvaluesRegisters
val returnRegs = prefixedFcall.target.targetSubroutine()?.asmReturnvaluesRegisters
if (returnRegs != null && returnRegs.size == 1 && returnRegs[0].statusflag != null) {
return codeForStatusflag(prefixedFcall, returnRegs[0].statusflag!!, true)
}
@ -502,6 +575,8 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position) })
val varbank = if(srcSub.asmAddress?.varbank==null) null else transform(srcSub.asmAddress!!.varbank!!)
if(varbank!=null && varbank !is PtIdentifier)
throw FatalAstException("varbank must be a regular variable")
val asmAddr = if(srcSub.asmAddress==null) null else {
val constAddr = srcSub.asmAddress!!.address.constValue(program)
if(constAddr==null) throw FatalAstException("extsub address should be a constant")
@ -551,11 +626,10 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
if(it.isString) DataType.UWORD else it
}
// do not bother about the 'inline' hint of the source subroutine.
val sub = PtSub(srcSub.name,
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position) },
returnTypes,
srcSub.position)
sub.parameters.forEach { it.parent=sub }
val sub = PtSub(srcSub.name,srcSub.position)
val signature = PtSubSignature( returnTypes, srcSub.position)
srcSub.parameters.forEach { signature.add(PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position)) }
sub.add(signature)
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
for (statement in statements)
sub.add(transformStatement(statement))
@ -575,6 +649,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
srcVar.datatype,
srcVar.zeropage,
srcVar.alignment,
srcVar.dirty,
value,
srcVar.arraysize?.constIndex()?.toUInt(),
srcVar.position
@ -585,6 +660,10 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
private fun transform(struct: StructDecl): PtStructDecl {
return PtStructDecl(struct.name, struct.fields, struct.position)
}
private fun transform(srcWhen: When): PtWhen {
val w = PtWhen(srcWhen.position)
w.add(transformExpression(srcWhen.condition))
@ -611,22 +690,44 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
private fun transform(src: AddressOf): PtAddressOf {
val addr = PtAddressOf(src.position, src.msb)
addr.add(transform(src.identifier))
if(src.arrayIndex!=null)
val addr = PtAddressOf(src.inferType(program).getOrUndef(), src.position, src.msb)
if(src.identifier!=null)
addr.add(transform(src.identifier!!))
if (src.arrayIndex != null)
addr.add(transformExpression(src.arrayIndex!!.indexExpr))
if (src.dereference!=null)
addr.add(transformExpression(src.dereference!!))
return addr
}
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val dt = srcArr.arrayvar.targetVarDecl(program)!!.datatype
if(!dt.isArray && !dt.isString)
throw FatalAstException("array indexing can only be used on array or string variables ${srcArr.position}")
val eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.arrayvar))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
if(srcArr.plainarrayvar!=null) {
val dt = srcArr.plainarrayvar!!.inferType(program)
if (!dt.isArray && !dt.isString && !dt.isPointer)
throw FatalAstException("array indexing can only be used on array, string or pointer variables ${srcArr.position}")
val eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.plainarrayvar!!))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
}
if(srcArr.pointerderef!=null) {
val dt = srcArr.pointerderef!!.inferType(program)
if(dt.isUnsignedWord) {
val array = PtArrayIndexer(DataType.UBYTE, srcArr.position)
array.add(transform(srcArr.pointerderef!!))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
} else if(dt.isPointer) {
val eltType = dt.getOrUndef().dereference()
val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.pointerderef!!))
array.add(transformExpression(srcArr.indexer.indexExpr))
return array
} else
TODO("transform pointer index $dt ${srcArr.position}")
}
throw FatalAstException("expected plain array variable or pointer dereference")
}
private fun transform(srcArr: ArrayLiteral): PtArray {
@ -639,12 +740,139 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
return arr
}
private fun transform(srcExpr: BinaryExpression): PtBinaryExpression {
val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position)
expr.add(transformExpression(srcExpr.left))
expr.add(transformExpression(srcExpr.right))
return expr
private fun transform(srcExpr: BinaryExpression): PtExpression {
val type = srcExpr.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcExpr")
}
if(srcExpr.operator==".") {
when (srcExpr.right) {
is IdentifierReference -> {
val arrayIndexer = srcExpr.left as? ArrayIndexedExpression
val valueName = srcExpr.right as? IdentifierReference
if (arrayIndexer == null) {
TODO("unexpected deref expression ${srcExpr.position}")
} else if(valueName?.nameInSource?.size!=1) {
TODO("unexpected deref expression ${srcExpr.position}")
} else {
// the expression is: a.b.c[i] . value
val result = PtBinaryExpression(".", type, srcExpr.position)
result.add(transformExpression(arrayIndexer))
result.add(PtIdentifier(valueName.nameInSource[0], type, srcExpr.position))
return result
}
}
is ArrayIndexedExpression -> {
errors.err("at the moment it is not possible to chain array syntax on pointers like ...p1[x].p2[y]... use separate expressions for the time being", srcExpr.right.position) // TODO add support for chained array syntax on pointers (rewrite ast?)
return PtIdentifier("<dummy>", DataType.UNDEFINED, Position.DUMMY)
}
else -> throw FatalAstException("unknown deref at ${srcExpr.position}")
}
} else {
if(srcExpr.left.inferType(program).isPointer || srcExpr.right.inferType(program).isPointer) {
return when (srcExpr.operator) {
"+", "-" -> transformWithPointerArithmetic(srcExpr)
in ComparisonOperators -> transformWithPointerComparison(srcExpr)
else -> throw FatalAstException("unsupported operator on pointer: ${srcExpr.operator} at ${srcExpr.position}")
}
} else {
val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position)
expr.add(transformExpression(srcExpr.left))
expr.add(transformExpression(srcExpr.right))
return expr
}
}
}
private fun transformWithPointerComparison(expr: BinaryExpression): PtBinaryExpression {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
require(leftDt.isPointer || leftDt.isWords)
require(rightDt.isPointer || rightDt.isWords)
val comparison = PtBinaryExpression(expr.operator, DataType.BOOL, expr.position)
comparison.add(transformExpression(expr.left))
comparison.add(transformExpression(expr.right))
return comparison
}
private fun transformWithPointerArithmetic(expr: BinaryExpression): PtExpression {
val operator = expr.operator
require(operator=="+" || operator=="-")
// below where '+' is used, you can substitute '-'.
// pointer arithmetic: ptr + value
val leftDt = expr.left.inferType(program).getOrUndef()
val rightDt = expr.right.inferType(program).getOrUndef()
if(leftDt.isPointer && !rightDt.isPointer) {
val resultDt = leftDt
val structSize = leftDt.size(program.memsizer)
val constValue = expr.right.constValue(program)
if(constValue!=null) {
// ptr + constvalue * structsize
val total = constValue.number*structSize
if (total == 0.0)
return transformExpression(expr.left)
else {
val plusorminus = PtBinaryExpression(operator, resultDt, expr.position)
plusorminus.add(transformExpression(expr.left))
plusorminus.add(PtNumber(BaseDataType.UWORD, total, expr.position))
return plusorminus
}
} else {
if(structSize==1) {
// ptr + right, just keep it as it is
val plus = PtBinaryExpression("+", DataType.UWORD, expr.position)
plus.add(transformExpression(expr.left))
plus.add(transformExpression(expr.right))
return plus
} else {
// ptr + right * structSize
val total = PtBinaryExpression("*", DataType.UWORD, expr.position)
total.add(transformExpression(expr.right))
total.add(PtNumber(BaseDataType.UWORD, structSize.toDouble(), expr.position))
val plusorminus = PtBinaryExpression(operator, resultDt, expr.position)
plusorminus.add(transformExpression(expr.left))
plusorminus.add(total)
return plusorminus
}
}
} else if(!leftDt.isPointer && rightDt.isPointer) {
val resultDt = rightDt
val structSize = rightDt.size(program.memsizer)
val constValue = expr.left.constValue(program)
if(constValue!=null) {
// ptr + constvalue * structsize
val total = constValue.number*structSize
if (total == 0.0)
return transformExpression(expr.right)
else {
val plusorminus = PtBinaryExpression(operator, resultDt, expr.position)
plusorminus.add(transformExpression(expr.right))
plusorminus.add(PtNumber(BaseDataType.UWORD, total, expr.position))
return plusorminus
}
} else {
if(structSize==1) {
// ptr + left, just keep it as it is
val plus = PtBinaryExpression("+", DataType.UWORD, expr.position)
plus.add(transformExpression(expr.left))
plus.add(transformExpression(expr.right))
return plus
} else {
// ptr + left * structSize
val total = PtBinaryExpression("*", DataType.UWORD, expr.position)
total.add(transformExpression(expr.left))
total.add(PtNumber(BaseDataType.UWORD, structSize.toDouble(), expr.position))
val plusorminus = PtBinaryExpression(operator, resultDt, expr.position)
plusorminus.add(transformExpression(expr.right))
plusorminus.add(total)
return plusorminus
}
}
} else {
throw FatalAstException("weird pointer arithmetic ${expr.position}")
}
}
private fun transform(srcCheck: ContainmentCheck): PtExpression {
@ -666,14 +894,14 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
expr.add(high)
} else {
val low = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
val lowFloat = PtTypeCast(BaseDataType.FLOAT, range.from.position)
val lowFloat = PtTypeCast(DataType.FLOAT, range.from.position)
lowFloat.add(transformExpression(range.from))
low.add(lowFloat)
low.add(x1)
expr.add(low)
val high = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
high.add(x2)
val highFLoat = PtTypeCast(BaseDataType.FLOAT, range.to.position)
val highFLoat = PtTypeCast(DataType.FLOAT, range.to.position)
highFLoat.add(transformExpression(range.to))
high.add(highFLoat)
expr.add(high)
@ -764,11 +992,11 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcCast: TypecastExpression): PtTypeCast {
val cast = PtTypeCast(srcCast.type, srcCast.position)
cast.add(transformExpression(srcCast.expression))
require(cast.type!=cast.value.type)
require(cast.type!=cast.value.type) {
"bogus typecast shouldn't occur at ${srcCast.position}" }
return cast
}
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (SourceCode.isLibraryResource(filename)) {
return com.github.michaelbull.result.runCatching {

View File

@ -1,12 +1,76 @@
package prog8.compiler.astprocessing
import prog8.ast.FatalAstException
import prog8.code.StStruct
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
internal fun postprocessSimplifiedAst(program: PtProgram, st: SymbolTable, errors: IErrorReporter) {
internal fun postprocessSimplifiedAst(
program: PtProgram,
st: SymbolTable,
option: CompilationOptions,
errors: IErrorReporter
) {
processDefers(program, st, errors)
processSubtypesIntoStReferences(program, st)
if(option.compTarget.cpu!=CpuType.VIRTUAL)
checkForPointerTypesOn6502(program, errors) // TODO remove this once the 6502 codegen can deal with pointer types and structs
}
private fun checkForPointerTypesOn6502(program: PtProgram, errors: IErrorReporter) {
fun check(node: PtNode) {
when(node) {
//is PtAddressOf -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtAssignTarget -> if(!node.void && node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
//is PtBinaryExpression -> if(node.left.type.isPointer || node.right.type.isPointer) errors.err("cannot do pointer arithmetic yet on 6502 target $node", node.position)
is PtIdentifier -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtPointerDeref -> errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtPrefix -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtTypeCast -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtStructDecl -> errors.err("cannot use struct type yet on 6502 target $node", node.position)
is PtSubroutineParameter -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtVariable -> if(node.type.isPointer) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
is PtSubSignature -> if(node.returns.any{it.isPointer}) errors.err("cannot use pointer type yet on 6502 target $node", node.position)
else -> {}
}
}
walkAst(program) { node, _ -> check(node) }
}
private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) {
fun getStStruct(subType: ISubType): StStruct {
val stNode = st.lookup(subType.scopedNameString) as? StStruct
if(stNode != null)
return stNode
else
throw FatalAstException("cannot find in ST: ${subType.scopedNameString} $subType")
}
fun fixSubtype(type: DataType) {
if(type.subType!=null && type.subType !is StStruct) {
type.subType = getStStruct(type.subType!!)
}
}
fun fixSubtypes(node: PtNode) {
when(node) {
is IPtVariable -> fixSubtype(node.type)
is PtPointerDeref -> fixSubtype(node.type)
is PtStructDecl -> node.fields.forEach { fixSubtype(it.first) }
is PtAsmSub -> node.returns.forEach { fixSubtype(it.second) }
is PtExpression -> fixSubtype(node.type)
is PtSubSignature -> node.returns.forEach { fixSubtype(it) }
is PtSubroutineParameter -> fixSubtype(node.type)
else -> { /* has no datatype */ }
}
}
walkAst(program) { node, _ -> fixSubtypes(node) }
}
@ -46,6 +110,7 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub
DataType.UBYTE,
ZeropageWish.NOT_IN_ZEROPAGE,
0u,
true,
null,
null,
sub.position
@ -55,8 +120,9 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub
it.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, sub.position))
})
assignZero.add(PtNumber(BaseDataType.UBYTE, 0.0, sub.position))
sub.add(0, assignZero)
sub.add(0, deferVariable)
val firstIndex = sub.children.indexOfFirst { it !is PtSubSignature } // first child node is the sub's signature so add below that one
sub.add(firstIndex, assignZero)
sub.add(firstIndex, deferVariable)
for((deferIndex, defer) in defers.withIndex()) {
// replace the defer statement with one that enables the bit in the mask for this defer
@ -159,9 +225,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
popCalls.forEach { newRet.add(it) }
group.add(PtFunctionCall(ret.definingSub()!!.scopedName+"."+invokeDefersRoutineName, true,DataType.UNDEFINED, ret.position))
group.add(newRet)
group.parent = ret.parent
val idx = ret.parent.children.indexOf(ret)
ret.parent.children[idx] = group
replaceNode(ret, group)
}
// subroutine ends
@ -197,9 +261,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
})
branchcc.add(PtNodeGroup())
defersRoutine.add(branchcc)
for(c in defer.children) {
defersRoutine.add(c)
}
transferChildren(defer, defersRoutine, true)
defersRoutine.add(PtLabel(skiplabel, Position.DUMMY))
}
// val printMask = PtFunctionCall("txt.print_ubbin", true, DataType.UNDEFINED, Position.DUMMY)
@ -211,3 +273,18 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
sub.add(defersRoutine)
}
}
private fun transferChildren(source: PtNode, target: PtNode, keepExisting: Boolean = false) {
if(!keepExisting)
target.children.clear()
for(c in source.children)
target.add(c)
}
private fun replaceNode(oldNode: PtNode, newNode: PtNode) {
newNode.parent = oldNode.parent
val idx = oldNode.parent.children.indexOf(oldNode)
oldNode.parent.children[idx] = newNode
}

View File

@ -49,10 +49,11 @@ internal class StatementReorderer(
if(decl.dirty && decl.value!=null)
errors.err("dirty variable can't have initialization value", decl.position)
if (decl.datatype.isNumericOrBool) {
if (decl.datatype.isNumericOrBool || decl.datatype.isPointer) {
if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) {
if (decl.value == null || decl.value?.constValue(program)?.number==0.0) {
decl.value = null
if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) {
if(decl.dirty) {
// no initialization at all!
@ -62,13 +63,18 @@ internal class StatementReorderer(
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
val canskip = canSkipInitializationWith0(decl)
if (!canskip) {
// (this explicit initialization assignment to 0 is not required for global variables in the block scope, these are zeroed as part of the BSS section clear)
if (!canSkipInitializationWith0(decl)) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position),
val assignzero = Assignment(AssignTarget(
identifier,
null,
null,
null,
false,
position = decl.position
),
decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
@ -81,7 +87,7 @@ internal class StatementReorderer(
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
val assign = Assignment(AssignTarget(identifier, null, null, null, false, position = pos),
decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
@ -96,11 +102,11 @@ internal class StatementReorderer(
// (that code only triggers on regular assignment, not on variable initializers)
val ident = decl.value as? IdentifierReference
if(ident!=null) {
val target = ident.targetVarDecl(program)
val target = ident.targetVarDecl()
if(target!=null && target.isArray) {
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
val assign = Assignment(AssignTarget(identifier, null, null, null, false, position = pos),
decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
@ -115,6 +121,11 @@ internal class StatementReorderer(
}
private fun canSkipInitializationWith0(decl: VarDecl): Boolean {
// if the variable is declared in a block, we can omit the init with 0 because
// the variable will be initialized to zero when the BSS section is cleared as a whole.
if(decl.parent is Block)
return true
// if there is an assignment to the variable below it (regular assign, or For loop),
// and there is nothing important in between, we can skip the initialization.
val statements = (decl.parent as? IStatementContainer)?.statements ?: return false
@ -123,14 +134,14 @@ internal class StatementReorderer(
when(stmt) {
is Assignment -> {
if (!stmt.isAugmentable) {
val assignTargets = stmt.target.multi?.mapNotNull { it.identifier?.targetVarDecl(program) }
val assignTargets = stmt.target.multi?.mapNotNull { it.identifier?.targetVarDecl() }
if(assignTargets!=null) {
if(decl in assignTargets) {
stmt.origin = AssignmentOrigin.VARINIT
return true
}
} else {
val assignTgt = stmt.target.identifier?.targetVarDecl(program)
val assignTgt = stmt.target.identifier?.targetVarDecl()
if (assignTgt == decl) {
stmt.origin = AssignmentOrigin.VARINIT
return true
@ -143,11 +154,11 @@ internal class StatementReorderer(
is ChainedAssignment -> {
var chained: ChainedAssignment? = stmt
while(chained!=null) {
val assignTgt = chained.target.identifier?.targetVarDecl(program)
val assignTgt = chained.target.identifier?.targetVarDecl()
if (assignTgt == decl)
return true
if(chained.nested is Assignment) {
if ((chained.nested as Assignment).target.identifier?.targetVarDecl(program) == decl) {
if ((chained.nested as Assignment).target.identifier?.targetVarDecl() == decl) {
(chained.nested as Assignment).origin = AssignmentOrigin.VARINIT
return true
}
@ -325,7 +336,7 @@ internal class StatementReorderer(
private fun checkCopyArrayValue(assign: Assignment) {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!!
val targetVar = identifier.targetVarDecl()!!
if(targetVar.arraysize==null) {
errors.err("array has no defined size", assign.position)
@ -340,7 +351,7 @@ internal class StatementReorderer(
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!!
val sourceVar = sourceIdent.targetVarDecl()!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
} else {

View File

@ -66,8 +66,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valueDt isNotAssignableTo decl.datatype)
return noModifications
// uwords are allowed to be assigned to pointers as initialization value without a cast
if(decl.datatype.isPointer && valueDt.isUnsignedWord)
return noModifications
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, declValue, decl.datatype.base, decl)
addTypecastOrCastedValueModification(modifications, declValue, decl.datatype, decl)
return modifications
}
}
@ -98,7 +102,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// if(rightDt.isBytes)
// modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
} else if (parent is TypecastExpression && parent.type == BaseDataType.UWORD && parent.parent is Assignment) {
} else if (parent is TypecastExpression && parent.type.isUnsignedWord && parent.parent is Assignment) {
val assign = parent.parent as Assignment
if (assign.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
@ -131,34 +135,34 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(leftDt issimpletype BaseDataType.BYTE && (rightDt issimpletype BaseDataType.UBYTE || rightDt issimpletype BaseDataType.UWORD)) {
// cast left to unsigned
val cast = TypecastExpression(expr.left, rightDt.getOrUndef().base, true, expr.left.position)
val cast = TypecastExpression(expr.left, rightDt.getOrUndef(), true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
if(leftDt issimpletype BaseDataType.WORD && (rightDt issimpletype BaseDataType.UBYTE || rightDt issimpletype BaseDataType.UWORD)) {
// cast left to unsigned word. Cast right to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.left, BaseDataType.UWORD, true, expr.left.position)
val cast = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
mods += IAstModification.ReplaceNode(expr.left, cast, expr)
if(rightDt issimpletype BaseDataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.right,
TypecastExpression(expr.right, BaseDataType.UWORD, true, expr.right.position),
TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position),
expr)
}
return mods
}
if(rightDt issimpletype BaseDataType.BYTE && (leftDt issimpletype BaseDataType.UBYTE || leftDt issimpletype BaseDataType.UWORD)) {
// cast right to unsigned
val cast = TypecastExpression(expr.right, leftDt.getOrUndef().base, true, expr.right.position)
val cast = TypecastExpression(expr.right, leftDt.getOrUndef(), true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
if(rightDt issimpletype BaseDataType.WORD && (leftDt issimpletype BaseDataType.UBYTE || leftDt issimpletype BaseDataType.UWORD)) {
// cast right to unsigned word. Cast left to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.right, BaseDataType.UWORD, true, expr.right.position)
val cast = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
mods += IAstModification.ReplaceNode(expr.right, cast, expr)
if(leftDt issimpletype BaseDataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.left,
TypecastExpression(expr.left, BaseDataType.UWORD, true, expr.left.position),
TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position),
expr)
}
return mods
@ -176,14 +180,57 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
} else {
val modifications = mutableListOf<IAstModification>()
when {
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt.base, expr)
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt.base, expr)
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
else -> throw FatalAstException("confused binary expression side")
}
return modifications
}
}
}
// comparison of a pointer with a number will simply treat the pointer as the uword that it is
// this may require casting the other operand to uword as well
if(expr.operator in ComparisonOperators) {
val modifications = mutableListOf<IAstModification>()
if(leftDt.isNumeric && rightDt.isPointer) {
val cast = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
modifications += IAstModification.ReplaceNode(expr.right, cast, expr)
if(!leftDt.isUnsignedWord && leftDt isAssignableTo InferredTypes.knownFor(BaseDataType.UWORD)) {
val cast2 = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
modifications += IAstModification.ReplaceNode(expr.left, cast2, expr)
}
}
else if(leftDt.isPointer && rightDt.isNumeric) {
val cast = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
modifications += IAstModification.ReplaceNode(expr.left, cast, expr)
if(!rightDt.isUnsignedWord && rightDt isAssignableTo InferredTypes.knownFor(BaseDataType.UWORD)) {
val cast2 = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
modifications += IAstModification.ReplaceNode(expr.right, cast2, expr)
}
}
return modifications
}
// pointer arithmetic
if(leftDt.isPointer) {
val cast = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
} else if(rightDt.isPointer) {
val cast = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
// check if shifts have a positive integer shift type
if(expr.operator=="<<" || expr.operator==">>") {
if(rightDt.isInteger) {
val rconst = expr.right.constValue(program)
if(rconst!=null && rconst.number<0)
errors.err("can only shift by a positive amount", expr.right.position)
} else
errors.err("right operand of bit shift must be an integer", expr.right.position)
}
}
@ -203,7 +250,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, assignment.value, targettype.base, assignment)
addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
return modifications
} else {
fun castLiteral(cvalue2: NumericLiteral): List<IAstModification.ReplaceNode> {
@ -233,49 +280,63 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter type
val modifications = mutableListOf<IAstModification>()
val params = when(val sub = call.target.targetStatement(program)) {
is BuiltinFunctionPlaceholder -> BuiltinFunctions.getValue(sub.name).parameters.toList()
is Subroutine -> sub.parameters.map { FParam(it.name, it.type.base) }
val paramsPossibleDatatypes = when(val sub = call.target.targetStatement(program.builtinFunctions)) {
is BuiltinFunctionPlaceholder -> {
BuiltinFunctions.getValue(sub.name).parameters.map {
it.possibleDatatypes.map { dt ->
if(dt.isArray)
DataType.arrayFor(BaseDataType.BOOL, false) // the builtin function signature doesn't tell us the element type....
else if(dt.isPointer)
DataType.pointer(BaseDataType.UBYTE)
else
DataType.forDt(dt)
}
}
}
is Subroutine -> sub.parameters.map { listOf(it.type) }
is StructDecl -> sub.fields.map { listOf(it.first) }
else -> emptyList()
}
params.zip(call.args).forEach {
val targetDt = it.first.possibleDatatypes.first()
paramsPossibleDatatypes.zip(call.args).forEach {
val possibleTargetDt = it.first.first()
val targetDt = if(possibleTargetDt.isPointer) DataType.UWORD else possibleTargetDt // use UWORD instead of a pointer type (using words for pointers is allowed without further casting)
val argIdt = it.second.inferType(program)
if (argIdt.isKnown) {
if (argIdt.isKnown && !targetDt.isStructInstance) {
val argDt = argIdt.getOrUndef()
if (argDt.base !in it.first.possibleDatatypes) {
if (argDt !in it.first) {
val identifier = it.second as? IdentifierReference
val number = it.second as? NumericLiteral
if(number!=null) {
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
} else if(identifier!=null && targetDt==BaseDataType.UWORD && argDt.isPassByRef) {
if(!identifier.isSubroutineParameter(program)) {
} else if(identifier!=null && targetDt.isUnsignedWord && argDt.isPassByRef) {
if(!identifier.isSubroutineParameter()) {
// We allow STR/ARRAY values for UWORD parameters.
// If it's an array (not STR), take the address.
if(!argDt.isString) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, false, it.second.position),
AddressOf(identifier, null, null, false, it.second.position),
call as Node
)
}
}
} else if(targetDt==BaseDataType.BOOL) {
addTypecastOrCastedValueModification(modifications, it.second, BaseDataType.BOOL, call as Node)
} else if(!targetDt.isIterable && argDt isAssignableTo DataType.forDt(targetDt)) {
if(!argDt.isString || targetDt!=BaseDataType.UWORD)
} else if(targetDt.isBool) {
addTypecastOrCastedValueModification(modifications, it.second, DataType.BOOL, call as Node)
} else if(!targetDt.isIterable && argDt isAssignableTo targetDt) {
if(!argDt.isString || !targetDt.isUnsignedWord)
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
}
}
} else {
val identifier = it.second as? IdentifierReference
if(identifier!=null && targetDt==BaseDataType.UWORD) {
if(identifier!=null && targetDt.isUnsignedWord) {
// take the address of the identifier
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, false, it.second.position),
AddressOf(identifier, null, null, false, it.second.position),
call as Node
)
}
@ -286,7 +347,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type==BaseDataType.FLOAT) {
if(typecast.implicit && typecast.type.isFloat) {
if(options.floats)
errors.info("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
else
@ -297,30 +358,32 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
// make sure the memory address is an uword or a pointer (to whatever type), otherwise cast
val dt = memread.addressExpression.inferType(program).getOr(DataType.UWORD)
if(dt.isUndefined || dt.isUnsignedWord || dt.isPointer)
return noModifications
val modifications = mutableListOf<IAstModification>()
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && !dt.getOr(DataType.UWORD).isUnsignedWord) {
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(BaseDataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
addTypecastOrCastedValueModification(modifications, memread.addressExpression, BaseDataType.UWORD, memread)
}
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(BaseDataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
addTypecastOrCastedValueModification(modifications, memread.addressExpression, DataType.UWORD, memread)
return modifications
}
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
// make sure the memory address is an uword or a pointer (to whatever type), otherwise cast
val dt = memwrite.addressExpression.inferType(program).getOr(DataType.UWORD)
if(dt.isUndefined || dt.isUnsignedWord || dt.isPointer)
return noModifications
val modifications = mutableListOf<IAstModification>()
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && !dt.getOr(DataType.UWORD).isUnsignedWord) {
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(BaseDataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, BaseDataType.UWORD, memwrite)
}
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(BaseDataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, DataType.UWORD, memwrite)
return modifications
}
@ -348,12 +411,18 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
continue
if (returnValue is NumericLiteral) {
val cast = returnValue.cast(subReturnType.base, true)
if(cast.isValid) {
returnStmt.values[index] = cast.valueOrZero()
if((returnValue.type == BaseDataType.UWORD || returnValue.type == BaseDataType.UBYTE) && subReturnType.isPointer) {
// cast unsigned integer to the number type
val cast = TypecastExpression(returnValue, subReturnType, true, returnValue.position)
modifications += IAstModification.ReplaceNode(returnValue, cast, returnStmt)
} else {
val cast = returnValue.cast(subReturnType.base, true)
if (cast.isValid) {
returnStmt.values[index] = cast.valueOrZero()
}
}
} else {
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType.base, returnStmt)
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
continue
}
}
@ -386,7 +455,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val varDt = when (parent) {
is ContainmentCheck -> parent.element.inferType(program)
is ForLoop -> parent.loopVarDt(program)
else -> InferredTypes.InferredType.unknown()
else -> InferredTypes.unknown()
}
return adjustRangeDts(range, fromConst, fromDt, toConst, toDt, varDt.getOrUndef(), parent)
}
@ -398,9 +467,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
for((index, elt) in array.value.withIndex()) {
if (elt is IdentifierReference) {
val eltType = elt.inferType(program)
val tgt = elt.targetStatement(program)
val tgt = elt.targetStatement()
if(eltType.isIterable || tgt is Subroutine || tgt is Label || tgt is Block) {
val addressof = AddressOf(elt, null, false, elt.position)
val addressof = AddressOf(elt, null, null, false, elt.position)
addressof.linkParents(array)
array.value[index] = addressof
}
@ -411,6 +480,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
override fun after(ifExpr: IfExpression, parent: Node): Iterable<IAstModification> {
val ct = ifExpr.condition.inferType(program)
if(!ct.isBool && (ct.isNumeric || ct.isPointer)) {
val cast = TypecastExpression(ifExpr.condition, DataType.BOOL, true, ifExpr.condition.position)
return listOf(IAstModification.ReplaceNode(ifExpr.condition, cast, ifExpr))
}
val trueDt = ifExpr.truevalue.inferType(program)
val falseDt = ifExpr.falsevalue.inferType(program)
if (trueDt != falseDt) {
@ -422,13 +497,40 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
)
if (toFix != null) {
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, toFix, commonDt.base, ifExpr)
addTypecastOrCastedValueModification(modifications, toFix, commonDt, ifExpr)
return modifications
}
}
return noModifications
}
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val ct = whileLoop.condition.inferType(program)
if(!ct.isBool && (ct.isNumeric || ct.isPointer)) {
val cast = TypecastExpression(whileLoop.condition, DataType.BOOL, true, whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, cast, whileLoop))
}
return noModifications
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val ct = ifElse.condition.inferType(program)
if(!ct.isBool && (ct.isNumeric || ct.isPointer)) {
val cast = TypecastExpression(ifElse.condition, DataType.BOOL, true, ifElse.condition.position)
return listOf(IAstModification.ReplaceNode(ifElse.condition, cast, ifElse))
}
return noModifications
}
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val ct = untilLoop.condition.inferType(program)
if(!ct.isBool && (ct.isNumeric || ct.isPointer)) {
val cast = TypecastExpression(untilLoop.condition, DataType.BOOL, true, untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, cast, untilLoop))
}
return noModifications
}
private fun adjustRangeDts(
range: RangeExpression,
fromConst: NumericLiteral?,
@ -524,7 +626,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val (commonDt, toChange) = BinaryExpression.commonDatatype(fromDt, toDt, range.from, range.to)
if(toChange!=null)
addTypecastOrCastedValueModification(modifications, toChange, commonDt.base, range)
addTypecastOrCastedValueModification(modifications, toChange, commonDt, range)
return modifications
}
@ -536,7 +638,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val idxDt = arrayIndexedExpression.indexer.indexExpr.inferType(program).getOrUndef()
if(idxDt.base.largerSizeThan(smaller.type)) {
val newIdx = ArrayIndex(smaller, smaller.position)
val newIndexer = ArrayIndexedExpression(arrayIndexedExpression.arrayvar, newIdx, arrayIndexedExpression.position)
val newIndexer = ArrayIndexedExpression(arrayIndexedExpression.plainarrayvar, arrayIndexedExpression.pointerderef, newIdx, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, newIndexer, parent))
}
}
@ -546,16 +648,33 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
private fun addTypecastOrCastedValueModification(
modifications: MutableList<IAstModification>,
expressionToCast: Expression,
requiredType: BaseDataType,
requiredType: DataType,
parent: Node
) {
val sourceDt = expressionToCast.inferType(program).getOrUndef()
if(sourceDt.base == requiredType)
if(sourceDt.base == requiredType.base)
return
if(requiredType == BaseDataType.BOOL)
if(requiredType.isBool) {
if(sourceDt.isNumeric || sourceDt.isPointer) {
// only allow numerics and pointers to be implicitly cast to bool
val cast = TypecastExpression(expressionToCast, DataType.BOOL, true, expressionToCast.position)
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
}
return
if(expressionToCast is NumericLiteral && expressionToCast.type!=BaseDataType.FLOAT) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType, true)
}
// uwords are allowed to be assigned to pointers without a cast
if(requiredType.isPointer && sourceDt.isUnsignedWord)
return
if (requiredType.isUnsignedWord) {
// & (address-of) is allowed to be assigned to an uword without a cast
if (expressionToCast is AddressOf) return
// casting a pointer to an uword is not needed
if (expressionToCast.inferType(program).isPointer) return
}
if(expressionToCast is NumericLiteral && expressionToCast.type!=BaseDataType.FLOAT && requiredType.isNumericOrBool) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType.base, true)
if (castedValue.isValid) {
val signOriginal = sign(expressionToCast.number)
val signCasted = sign(castedValue.valueOrZero().number)
@ -565,6 +684,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return
}
}
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
}

View File

@ -4,29 +4,28 @@ import prog8.ast.FatalAstException
import prog8.code.ast.PtExpression
import prog8.code.ast.PtFunctionCall
import prog8.code.ast.PtTypeCast
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall, PtExpression> {
var popTypecast: BaseDataType? = null
var pushTypecast: BaseDataType? = null
var popTypecast: DataType? = null
var pushTypecast: DataType? = null
var pushWord = false
var pushFloat = false
when {
value.type.isBool -> {
pushTypecast = BaseDataType.UBYTE
popTypecast = BaseDataType.BOOL
pushTypecast = DataType.UBYTE
popTypecast = DataType.BOOL
}
value.type.isSignedByte -> {
pushTypecast = BaseDataType.UBYTE
popTypecast = BaseDataType.BYTE
pushTypecast = DataType.UBYTE
popTypecast = DataType.BYTE
}
value.type.isSignedWord -> {
pushWord = true
pushTypecast = BaseDataType.UWORD
popTypecast = BaseDataType.WORD
pushTypecast = DataType.UWORD
popTypecast = DataType.WORD
}
value.type.isUnsignedByte -> {}
value.type.isUnsignedWord -> pushWord = true

View File

@ -24,10 +24,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// check and possibly adjust value datatype vs decl datatype
val valueType = decl.value?.inferType(program)
if(valueType!=null && !valueType.istype(decl.datatype)) {
if(valueType.isUnknown) {
errors.err("value has incompatible type for ${decl.datatype}", decl.value!!.position)
if(valueType.isUnknown)
return noModifications
}
val valueDt = valueType.getOrUndef()
when(decl.type) {
VarDeclType.VAR -> {
@ -41,8 +39,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
errors.err("value '$constValue' out of range for ${decl.datatype}", constValue.position)
else
errors.err("value out of range for ${decl.datatype}", decl.value!!.position)
} else {
throw FatalAstException("value dt differs from decl dt ${decl.position}")
}
}
}
@ -70,11 +66,12 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
// check splitting of word arrays
if(!decl.datatype.isWordArray && decl.splitwordarray != SplitWish.DONTCARE) {
if(decl.splitwordarray != SplitWish.DONTCARE && !decl.datatype.isWordArray && !decl.datatype.isPointerArray) {
if(decl.origin != VarDeclOrigin.ARRAYLITERAL)
errors.err("@split and @nosplit are for word arrays only", decl.position)
errors.err("@split and @nosplit are for word or pointer arrays only", decl.position)
}
else if(decl.datatype.isWordArray) {
if(decl.datatype.isWordArray) {
var changeDataType: DataType?
var changeSplit: SplitWish = decl.splitwordarray
when(decl.splitwordarray) {
@ -84,15 +81,29 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
changeSplit = SplitWish.NOSPLIT
}
else {
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base, true)
changeDataType = if(decl.datatype.isSplitWordArray) null else {
val eltDt = decl.datatype.elementType()
if(eltDt.isPointer)
TODO("convert array of pointers to split words array type")
else
DataType.arrayFor(eltDt.base)
}
changeSplit = SplitWish.SPLIT
}
}
SplitWish.SPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray) null else DataType.arrayFor(decl.datatype.elementType().base, true)
changeDataType = if(decl.datatype.isSplitWordArray) null else {
val eltDt = decl.datatype.elementType()
if(eltDt.isPointer)
TODO("convert array of pointers to split words array type")
else
DataType.arrayFor(eltDt.base)
}
}
SplitWish.NOSPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray) DataType.arrayFor(decl.datatype.elementType().base, false) else null
changeDataType = if(decl.datatype.isSplitWordArray && !decl.datatype.elementType().isPointer)
DataType.arrayFor(decl.datatype.elementType().base, false)
else null
}
}
if(changeDataType!=null) {
@ -132,14 +143,17 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(constValue!=null)
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
if(typecast.expression is NumericLiteral) {
val value = (typecast.expression as NumericLiteral).cast(typecast.type, typecast.implicit)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
val number = typecast.expression as? NumericLiteral
if(number!=null) {
if(typecast.type.isBasic) {
val value = number.cast(typecast.type.base, typecast.implicit)
if (value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt issimpletype typecast.type)
if(sourceDt istype typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(parent is Assignment) {
@ -150,6 +164,26 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
}
// number cast to bool -> number!=0
if (typecast.type.isBool) {
val et = typecast.expression.inferType(program)
if (et.isNumeric) {
if(typecast.expression is NumericLiteral) {
val boolean = NumericLiteral.fromBoolean((typecast.expression as NumericLiteral).asBooleanValue, typecast.expression.position)
return listOf(IAstModification.ReplaceNode(typecast, boolean, parent))
} else {
val zero = defaultZero(et.getOrUndef().base, typecast.position)
val cmp = BinaryExpression(typecast.expression, "!=", zero, typecast.position)
return listOf(IAstModification.ReplaceNode(typecast, cmp, parent))
}
}
else if (et.isPointer) {
val ptrAsUword = TypecastExpression(typecast.expression, DataType.UWORD, true, typecast.position)
val cmp = BinaryExpression(ptrAsUword, "!=", NumericLiteral.optimalNumeric(BaseDataType.UWORD, null, 0.0, typecast.position), typecast.position)
return listOf(IAstModification.ReplaceNode(typecast, cmp, parent))
}
}
return noModifications
}
@ -285,7 +319,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// replace x==1 or x==2 or x==3 with a containment check x in [1,2,3]
val valueCopies = values.sortedBy { it.number }.map { it.copy() }
val arrayType = DataType.arrayFor(elementType.base, true)
val arrayType = DataType.arrayFor(elementType.base)
val valuesArray = ArrayLiteral(InferredTypes.InferredType.known(arrayType), valueCopies.toTypedArray(), expr.position)
val containment = ContainmentCheck(needle, valuesArray, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
@ -394,17 +428,21 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val index = arrayIndexedExpression.indexer.constIndex()
if(index!=null && index<0) {
val target = arrayIndexedExpression.arrayvar.targetVarDecl(program)
val arraysize = target?.arraysize?.constIndex()
if(arraysize!=null) {
if(arraysize+index < 0) {
errors.err("index out of bounds", arrayIndexedExpression.position)
return noModifications
if(arrayIndexedExpression.plainarrayvar!=null) {
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
val arraysize = target?.arraysize?.constIndex()
if(arraysize!=null) {
if(arraysize+index < 0) {
errors.err("index out of bounds", arrayIndexedExpression.position)
return noModifications
}
// replace the negative index by the normal index
val newIndex = NumericLiteral.optimalNumeric(arraysize+index, arrayIndexedExpression.indexer.position)
arrayIndexedExpression.indexer.indexExpr = newIndex
newIndex.linkParents(arrayIndexedExpression.indexer)
}
// replace the negative index by the normal index
val newIndex = NumericLiteral.optimalNumeric(arraysize+index, arrayIndexedExpression.indexer.position)
arrayIndexedExpression.indexer.indexExpr = newIndex
newIndex.linkParents(arrayIndexedExpression.indexer)
} else if(arrayIndexedExpression.pointerderef!=null) {
TODO("cleanup pointer indexing")
}
}
return noModifications
@ -414,20 +452,46 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
val name = functionCallExpr.target.nameInSource
if(name==listOf("msw")) {
val valueDt = functionCallExpr.args[0].inferType(program)
if(valueDt.isWords || valueDt.isBytes) {
if(valueDt.isWords || valueDt.isBytes || valueDt.isPointer) {
val zero = NumericLiteral(BaseDataType.UWORD, 0.0, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, zero, parent))
}
} else if(name==listOf("lsw")) {
val valueDt = functionCallExpr.args[0].inferType(program)
if(valueDt.isWords)
if(valueDt.isWords || valueDt.isPointer)
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
if(valueDt.isBytes) {
val cast = TypecastExpression(functionCallExpr.args[0], BaseDataType.UWORD, true, functionCallExpr.position)
val cast = TypecastExpression(functionCallExpr.args[0], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
if(parent is IStatementContainer) {
val targetStruct = functionCallExpr.target.targetStructDecl()
if (targetStruct != null) {
// static struct instance allocation can only occur as an initializer for a pointer variable
return listOf(IAstModification.Remove(functionCallExpr, parent as IStatementContainer))
}
}
return noModifications
}
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
if(addressOf.arrayIndex!=null) {
val tgt = addressOf.identifier?.constValue(program)
if (tgt != null && tgt.type.isWord) {
// &constant[idx] --> constant + idx
val indexExpr = addressOf.arrayIndex!!.indexExpr
val right = if(indexExpr.inferType(program) issimpletype tgt.type)
indexExpr
else
TypecastExpression(indexExpr, DataType.forDt(tgt.type), true, indexExpr.position)
val add = BinaryExpression(tgt, "+", right, addressOf.position)
return listOf(
IAstModification.ReplaceNode(addressOf, add, parent)
)
}
}
return noModifications
}
}

View File

@ -5,8 +5,8 @@ import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.*
internal class VerifyFunctionArgTypes(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : IAstVisitor {
@ -31,7 +31,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
}
override fun visit(identifier: IdentifierReference) {
if(identifier.wasStringLiteral(program)) {
if(identifier.wasStringLiteral()) {
allStringRefs.add(identifier.nameInSource)
}
}
@ -83,11 +83,24 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
if(argDt==paramDt)
return true
// there are some exceptions that are considered compatible, such as STR <> UWORD
if(argDt.isString && paramDt.isUnsignedWord ||
argDt.isUnsignedWord && paramDt.isString ||
argDt.isUnsignedWord && paramDt.isUnsignedByteArray ||
argDt.isString && paramDt.isUnsignedByteArray)
// there are some exceptions that are considered compatible, such as STR <> UWORD, UWORD <> pointer
if(argDt.isUnsignedWord)
if((paramDt.isString || paramDt.isUnsignedByteArray || paramDt.isPointer))
return true
if(argDt.isString) {
if(paramDt.isUnsignedWord || paramDt.isUnsignedByteArray)
return true
if(paramDt.isPointer && paramDt.sub==BaseDataType.UBYTE)
return true
}
// if uword is passed, check if the parameter type is pointer to array element type
if(argDt.isArray && paramDt.isPointer) {
TODO("array vs element pointer check")
}
// if expected is UWORD and actual is any pointer, we allow it (uword is untyped pointer, for backwards compatibility)
if(paramDt.isUnsignedWord && argDt.isPointer)
return true
return false
@ -97,14 +110,15 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0) {
// if an uword is expected but a pointer is provided, that is okay without a cast
val identifier = call.args[0] as? IdentifierReference
return if(identifier==null || identifier.targetStatement(program)!=null)
return if(identifier==null || identifier.targetStatement(program.builtinFunctions)!=null)
Pair("argument ${firstUnknownDt + 1} invalid argument type", call.args[firstUnknownDt].position)
else
null
}
val argtypes = argITypes.map { it.getOrUndef() }
val target = call.target.targetStatement(program)
val target = call.target.targetStatement(program.builtinFunctions)
if (target is Subroutine) {
val consideredParamTypes: List<DataType> = target.parameters.map { it.type }
if(argtypes.size != consideredParamTypes.size)
@ -113,6 +127,14 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
if(mismatch>=0) {
val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch]
if(expected.isPointer && expected.sub!!.isWord) {
val arg = call.args[mismatch]
val argArray = if(arg is AddressOf) arg.identifier else arg
if(argArray?.inferType(program)?.getOrUndef()?.isSplitWordArray==true)
return Pair("argument ${mismatch + 1} cannot pass address to a split words array where a word pointer argument is expected, use a @nosplit word array instead", call.args[mismatch].position)
else
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
}
if(target.isAsmSubroutine) {

View File

@ -77,7 +77,7 @@ main {
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 7
statements.size shouldBe 8
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment

View File

@ -7,6 +7,7 @@ import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.ast.statements.Block
@ -312,4 +313,20 @@ xyz {
val blocks2 = result2.codegenAst!!.allBlocks().toList()
blocks2.any { it.name=="xyz" } shouldBe false
}
test("symbol lookup of pointer fields should mark variable as used in callgraph") {
val src = """
main {
struct List {
^^uword s
ubyte n
}
sub start() {
^^List l1 = List()
l1.s^^ = 2
}
}"""
compileText(VMTarget(), true, src, outputDir, writeAssembly = true) shouldNotBe null
}
})

View File

@ -8,7 +8,6 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Program
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
@ -31,11 +30,11 @@ class TestCompilerOnCharLit: FunSpec({
val outputDir = tempdir().toPath()
fun findInitializer(vardecl: VarDecl, program: Program): Assignment? =
fun findInitializer(vardecl: VarDecl): Assignment? =
(vardecl.parent as IStatementContainer).statements
.asSequence()
.filterIsInstance<Assignment>()
.singleOrNull { it.origin== AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl(program) === vardecl }
.singleOrNull { it.origin== AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl() === vardecl }
test("testCharLitAsExtsubArg") {
@ -79,14 +78,14 @@ class TestCompilerOnCharLit: FunSpec({
funCall.args[0] shouldBe instanceOf<IdentifierReference>()
val arg = funCall.args[0] as IdentifierReference
val decl = arg.targetVarDecl(program)!!
val decl = arg.targetVarDecl()!!
decl.type shouldBe VarDeclType.VAR
decl.datatype shouldBe DataType.UBYTE
withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null
}
val assignInitialValue = findInitializer(decl, program)!!
val assignInitialValue = findInitializer(decl)!!
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
withClue("char literal should have been replaced by ubyte literal") {
assignInitialValue.value shouldBe instanceOf<NumericLiteral>()
@ -115,7 +114,7 @@ class TestCompilerOnCharLit: FunSpec({
// Now, both is ok for the arg: a) still the IdRef or b) replaced by numeric literal
when (val arg = funCall.args[0]) {
is IdentifierReference -> {
val decl = arg.targetVarDecl(program)!!
val decl = arg.targetVarDecl()!!
decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteral).number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0]

View File

@ -36,6 +36,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,
@ -156,6 +157,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"interpolation",
"kefrenbars",
"keyboardhandler",
"landscape",
"life",
"mandelbrot",
"multi-irq-old",
@ -239,7 +241,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
"bouncegfx",
"bsieve",
"pixelshader",
"sincos"
"sincos",
"pointers/sortedlist" // TODO add to "c64" later as well
),
listOf(false, true)
)

View File

@ -34,7 +34,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] as IdentifierReference }
.map { it.targetVarDecl(program)!!.value as StringLiteral }
.map { it.targetVarDecl()!!.value as StringLiteral }
strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar"
@ -57,12 +57,12 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
.filterIsInstance<FunctionCallStatement>()
.map { it.args[0] }
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteral
val str0 = (args[0] as IdentifierReference).targetVarDecl()!!.value as StringLiteral
str0.value shouldBe "main.bar"
str0.definingScope.name shouldBe "main"
val id1 = (args[1] as AddressOf).identifier
val lbl1 = id1.targetStatement(program) as Label
val id1 = (args[1] as AddressOf).identifier!!
val lbl1 = id1.targetStatement() as Label
lbl1.name shouldBe "foo_bar"
lbl1.definingScope.name shouldBe "main"
}

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