Compare commits

...

329 Commits

Author SHA1 Message Date
Irmen de Jong
aee53b14c7 avoid costly multiplications to get indexed pointer address 2025-08-29 20:18:26 +02:00
Irmen de Jong
5eb2fc8d86 fix a bad pointer arithmetic optimization 2025-08-29 19:36:57 +02:00
Irmen de Jong
221a093e5f optimize some pointer arithmetic, fix pointer arithmetic for ptr-value 2025-08-28 23:08:06 +02:00
Irmen de Jong
2ca1820d4e Merge branch 'master' into structs6502 2025-08-28 21:55:46 +02:00
Irmen de Jong
d58737d5be use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:46:09 +02:00
Irmen de Jong
b52cee3154 use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:19:16 +02:00
Irmen de Jong
9a76941e10 fix invalid optimization for ptr-value 2025-08-28 20:13:02 +02:00
Irmen de Jong
0285a4cce1 avoid needless pointer arithmetic multiplication by struct size 1 2025-08-27 21:50:09 +02:00
Irmen de Jong
5a3aa1bd25 optimized IR to return a constant value: use RETURNI 2025-08-26 22:54:11 +02:00
Irmen de Jong
0f79351de9 support word size indexing on typed pointers 2025-08-25 22:58:48 +02:00
Irmen de Jong
3cdb25ce8e fix invalid string comparisons being generated for regular pointer value comparison 2025-08-24 16:19:42 +02:00
Irmen de Jong
b7193bd0c6 update examples to use typed pointers where appropriate
(found 2 regressions that still need to be handled)
2025-08-24 15:17:47 +02:00
Irmen de Jong
10ff6a0095 Merge branch 'master' into structs6502 2025-08-24 14:34:15 +02:00
Irmen de Jong
d30f58004e converting untyped to typed pointers in custom target libraries 2025-08-24 14:27:25 +02:00
Irmen de Jong
17bdb22e4f converting untyped to typed pointers in c64,c128,pet32 libraries 2025-08-24 14:21:51 +02:00
Irmen de Jong
a68209be37 converting untyped to typed pointers in cx16 libraries 2025-08-24 14:13:02 +02:00
Irmen de Jong
7b40eade44 added txt.rvs_on() and txt.rvs_off(), added txt.color() support for virtual target 2025-08-24 13:29:29 +02:00
Irmen de Jong
8a717c74b9 sorting lib symboldumps 2025-08-24 13:07:10 +02:00
Irmen de Jong
aa72ded21e add sorting module to symboldump, moved shared cbm textio routines to shared module 2025-08-24 13:05:38 +02:00
Irmen de Jong
8e53c83844 optimize certain ptr+value expression on 6502 2025-08-24 05:46:49 +02:00
Irmen de Jong
e2a2db1256 converting untyped to typed pointers in libraries 2025-08-22 22:47:19 +02:00
markjreed
2afd2d4dae Make PET PLOT routine more efficient (#174)
* feat: take advantage of KERNAL vars to implement PLOT

* fix: tabs

* fix: update TBLX too, so cursor change sticks after output

* fix: formatting
2025-08-22 21:28:21 +02:00
Irmen de Jong
fad17cc094 support a few other simple cases of struct instance assignment 2025-08-21 00:04:30 +02:00
Irmen de Jong
f93d957999 proper warning for struct instance assignment (not yet supported) 2025-08-20 20:35:29 +02:00
Irmen de Jong
8db0344cee Merge branch 'master' into structs6502
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
2025-08-19 23:10:15 +02:00
Irmen de Jong
32e531f951 test 2025-08-19 23:09:33 +02:00
Irmen de Jong
a4769702f9 todo 2025-08-19 23:06:04 +02:00
Irmen de Jong
cf19fb8df1 fix 6502 struct type and field name prefixing and datatype references to stale structs from stale Symboltable 2025-08-19 20:47:37 +02:00
Irmen de Jong
79b8bb5c9f Merge branch 'master' into structs6502 2025-08-19 01:12:50 +02:00
Irmen de Jong
fc5889ec0b kotlin 2.2.10, kotest 2025-08-19 01:04:24 +02:00
Irmen de Jong
369303c46d fix register clobbering in pointer deref 2025-08-18 23:36:35 +02:00
Irmen de Jong
d65670cc7b fix 6502 address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 20:24:39 +02:00
Irmen de Jong
f74eeaee0f fix IR address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 03:24:32 +02:00
Irmen de Jong
826fb3e9c2 implemented assign indexed ptr byte 2025-08-17 13:21:31 +02:00
Irmen de Jong
a3d7b8a899 fix detection of string comparisons (make it aware of new pointer types equivalent to STR) 2025-08-16 17:04:59 +02:00
Irmen de Jong
0cc36ed6e4 pointer TODO's all as stub methods in the PointerAssignmentsGen class 2025-08-16 16:21:06 +02:00
Irmen de Jong
976bd52972 docs dark mode 2025-08-16 11:32:24 +02:00
Irmen de Jong
4a8d5def84 code cleanups, pointer TODOs, docs dark mode 2025-08-16 11:25:18 +02:00
Irmen de Jong
2f60716082 6502 struct allocation to asm file, struct name and field prefixing (maybe unneeded...) 2025-08-16 01:49:22 +02:00
Irmen de Jong
729efb04e1 fix code size regressions 2025-08-14 23:59:59 +02:00
Irmen de Jong
4ea8b4d445 fix unknown field test and redundant errors 2025-08-14 21:01:25 +02:00
Irmen de Jong
e800c165f9 fix 6502 inplace pointer variable assignment 2025-08-14 20:38:18 +02:00
Irmen de Jong
fd9bd23449 6502 statementreorderer: str -> ^^ubyte 2025-08-13 18:45:29 +02:00
Irmen de Jong
8880ed1393 fix address-of struct fields 2025-08-08 23:08:47 +02:00
Irmen de Jong
f7fde070ca fix boolean pointer condition in if expression 2025-08-08 22:15:58 +02:00
Irmen de Jong
5ada80779d Merge branch 'refs/heads/master' into structs6502
# Conflicts:
#	examples/test.p8
2025-08-07 21:25:07 +02:00
Irmen de Jong
8972235a0e fix missing peekbool() and pokebool() code gen 2025-08-07 21:22:48 +02:00
Irmen de Jong
e56f533e38 more basic pointer inplace operations 2025-08-07 00:36:47 +02:00
Irmen de Jong
324fb7dbf7 more basic pointer inplace operations (float)
basic pointers unit test now passes
2025-08-06 22:26:23 +02:00
Irmen de Jong
44285b9b5d more basic pointer inplace operations 2025-08-06 00:32:15 +02:00
Irmen de Jong
a68f477d61 Merge branch 'master' into structs6502
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-08-05 23:29:58 +02:00
Irmen de Jong
ae9f99448e fix pointer subtract arithmetic 2025-08-05 23:23:29 +02:00
Irmen de Jong
7c0fb10197 fix bug: VM MULR float error, another pointer dependency checker error 2025-08-05 22:00:05 +02:00
Irmen de Jong
9e85571a7b fix pointer variable usage detection in other block 2025-08-05 17:39:22 +02:00
Irmen de Jong
9e10c15e2e working on 6502 pointer inplace assignments 2025-08-04 23:32:17 +02:00
Irmen de Jong
6bd7752bac working on 6502 pointer dereferencing 2025-08-04 20:22:13 +02:00
Irmen de Jong
83ec437e8a testpointers unit test now also for 6502 targets
implementing first simple pointer operations
pointer vars also allocated in ZP for dontcare
2025-08-03 22:12:03 +02:00
Irmen de Jong
4a1d05dd46 first 6502 codegen results 2025-08-03 16:10:00 +02:00
Irmen de Jong
aa324e355a remove 6502 pointer check, TODOs for pointer assignments 2025-08-03 13:28:39 +02:00
Irmen de Jong
5cb8bcead7 todo 2025-08-03 00:54:43 +02:00
Irmen de Jong
bbd06c0c99 implement peekbool/pokebool on 6502, fix float assignment register error 2025-08-02 21:38:29 +02:00
Irmen de Jong
651830ea82 update syntax files 2025-08-02 21:00:16 +02:00
Irmen de Jong
c70146f1dc Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
2025-08-02 19:26:54 +02:00
Irmen de Jong
d4e83b28bb error messages and trying to improve support for struct allocs in arrays
added sorting example
2025-08-02 19:22:58 +02:00
Irmen de Jong
bc58a25765 allow sizeof(^^type) to return the size of a pointer 2025-08-02 11:33:15 +02:00
Irmen de Jong
38645022c9 actually disallow ^^str 2025-08-02 05:49:12 +02:00
Irmen de Jong
647cd0fbe1 fix pointer[i].field compiler crash 2025-08-02 05:29:16 +02:00
Irmen de Jong
ea8935a346 doc 2025-08-02 00:02:29 +02:00
Irmen de Jong
7ea80babfc todo 2025-08-01 23:38:49 +02:00
Irmen de Jong
dee761a99e fix compiler crash on certain pointer assignments 2025-08-01 22:22:43 +02:00
Irmen de Jong
88ee7a8187 fix expected outcome of function call arg type pointer test 2025-08-01 00:23:10 +02:00
Irmen de Jong
eb8b408b82 fix countries[2]^^ = 0 compiler crash 2025-07-31 02:03:18 +02:00
Irmen de Jong
3d10882f57 fix ast printing 2025-07-30 23:42:45 +02:00
Irmen de Jong
1988496512 correct commit hash 2025-07-30 01:08:22 +02:00
Irmen de Jong
88b074c208 pointer types should just be uwords in IR 2025-07-30 01:07:37 +02:00
Irmen de Jong
c4c5636a81 fixing array indexing on pointers 2025-07-29 23:41:38 +02:00
Irmen de Jong
c39d570b72 make more use of ISubType interface itself rather than casting it to StructDecl all the time 2025-07-29 22:59:31 +02:00
Irmen de Jong
4ccd7f9f3a improve docs about recursion 2025-07-29 22:49:24 +02:00
Irmen de Jong
1c9c5aeef7 todos 2025-07-28 23:29:18 +02:00
Irmen de Jong
23ad540aa5 fix IR codegen type error on array pointer dereference 2025-07-28 20:25:56 +02:00
Irmen de Jong
08810c2749 proper error message for unsupported &&subroutine 2025-07-27 23:44:24 +02:00
Irmen de Jong
a52966f327 rollback implicit casts to boolean in struct initializers and function call arguments 2025-07-27 03:27:26 +02:00
Irmen de Jong
624220e9a3 fix boolean struct field as if conditional 2025-07-27 01:58:18 +02:00
Irmen de Jong
842b11ed9e struct docs 2025-07-27 00:00:40 +02:00
Irmen de Jong
82267b3f56 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/codegeneration/TestVariables.kt
#	docs/source/_static/symboldumps/skeletons-c128.txt
#	docs/source/_static/symboldumps/skeletons-c64.txt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
#	docs/source/_static/symboldumps/skeletons-pet32.txt
#	docs/source/_static/symboldumps/skeletons-virtual.txt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-26 12:43:13 +02:00
Irmen de Jong
67fb45a55b don't produce invalid boolean initalization error. Fixes #173 2025-07-26 12:35:23 +02:00
Irmen de Jong
11186f1dbe make sure that the virtual target -emu (vm) only runs the actual .p8ir file (fixes #172)
added emudbg.console_nl()
2025-07-26 11:19:01 +02:00
Irmen de Jong
0116fac201 release 11.4.1 2025-07-24 23:00:10 +02:00
Irmen de Jong
817f4f8e7c Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
#	compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
#	compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-24 22:27:29 +02:00
Irmen de Jong
866313209b fixed zp vars 0 initialization 2025-07-24 00:17:31 +02:00
Irmen de Jong
28e351daab new library routine skeletons 2025-07-22 18:18:15 +02:00
Irmen de Jong
893e16d814 replace str or ubyte[] param and returnvalue type into ^^ubyte rather than uword 2025-07-21 22:59:14 +02:00
Irmen de Jong
33470c47fc last changes to virtual diskio to make untyped pointers typed 2025-07-21 22:13:36 +02:00
Irmen de Jong
63f7b87572 Merge branch 'master' into structs 2025-07-21 22:11:34 +02:00
Irmen de Jong
e2901cca1b fix virtual diskio save_raw() 2025-07-21 22:10:50 +02:00
Irmen de Jong
ce8006992a changing virtual diskio to use typed pointers 2025-07-21 22:10:04 +02:00
Irmen de Jong
0b5413ad83 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-07-21 20:53:06 +02:00
Irmen de Jong
dd7adde387 fix virtual diskio.f_write 2025-07-21 20:52:11 +02:00
Irmen de Jong
23058b51a1 started changing libs to typed pointers 2025-07-21 20:50:33 +02:00
Irmen de Jong
2f90c53ad0 started changing libs to typed pointers 2025-07-20 23:59:59 +02:00
Irmen de Jong
c3be7ab4b3 fix if expression type problems with pointers 2025-07-18 23:44:55 +02:00
Irmen de Jong
a9b8fbc6c6 Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-07-18 22:37:36 +02:00
Irmen de Jong
720988ae72 proper warnings for using pure builtin functions as a statement (discarding the result)
swallow a defer warning for a very common use case
2025-07-18 22:37:07 +02:00
Irmen de Jong
b0981a5fae pointers no longer implicitly converted to boolean in expressions, to be consistent with how integers are handled in conditionals
adding particles fountain examples
2025-07-18 01:43:35 +02:00
Irmen de Jong
ea5deeefbd new links to Codebase64 website 2025-07-17 23:06:39 +02:00
Irmen de Jong
054c98da7c add link to extra prog8 compilation targets 2025-07-15 00:17:04 +02:00
Irmen de Jong
dc434d034a fix address-of identifier alias replacement stripping array index away 2025-07-14 23:54:26 +02:00
Irmen de Jong
a4a1b563aa sdks 2025-07-14 21:54:56 +02:00
Irmen de Jong
3f34b83e0d sdks 2025-07-14 21:54:40 +02:00
Irmen de Jong
9c63ef39c7 fix pointer test 2025-07-07 23:09:45 +02:00
Irmen de Jong
9f6106452e revert & to untyped pointer, added && for typed pointer address-of 2025-07-07 16:17:07 +02:00
Irmen de Jong
f9fbfe30e3 fix &x +/- offset pointer arithmetic expression 2025-07-07 13:11:57 +02:00
Irmen de Jong
9a9bf170c6 Merge branch 'master' into structs 2025-07-06 23:17:00 +02:00
Irmen de Jong
7dd64b4f13 fix 'cpa' instruction generated in certain boolean assignment, must be 'cmp' 2025-07-06 22:43:19 +02:00
Irmen de Jong
b6c0bac96f identified problems with pointer array as parameter 2025-07-06 20:33:20 +02:00
Irmen de Jong
8ede098154 fix pointer array initialization 2025-07-06 19:42:54 +02:00
Irmen de Jong
2a4a3b786e cleanup error message for currently unsupported deref'd pointer assignments 2025-07-06 14:59:42 +02:00
Irmen de Jong
b4e0a2019e fixed assignment to a[i]^^ 2025-07-06 13:38:22 +02:00
Irmen de Jong
e14c3f8b59 code cleanups 2025-07-06 00:52:37 +02:00
Irmen de Jong
c81f76226d Merge branch 'master' into structs 2025-07-06 00:37:58 +02:00
Irmen de Jong
edc353cc24 more kotlin 2.2 settings 2025-07-06 00:37:15 +02:00
Irmen de Jong
dcce519c69 Merge branch 'master' into structs
# Conflicts:
#	.idea/libraries/KotlinJavaRuntime.xml
#	build.gradle.kts
#	gradle.properties
2025-07-06 00:07:16 +02:00
Irmen de Jong
0a16dcafc0 update kotlin and gradle wrapper 2025-07-06 00:01:24 +02:00
Irmen de Jong
54d41b7f6f fixed a[i]^^ 2025-07-05 23:54:08 +02:00
Irmen de Jong
0541b84d09 Merge branch 'master' into structs 2025-06-29 16:34:32 +02:00
gillham
1b420f7fe7 Add a preliminary external custom target for the Foenix F256 family of modern retro computers. (#171) 2025-06-29 11:14:34 +02:00
Irmen de Jong
6a9a82ff9d doc 2025-06-27 18:14:36 +02:00
markjreed
aa36e6b19f flesh out C128-specific KERNAL calls (#170)
* flesh out C128-specific KERNAL calls

* fix: typo in comment

* fix: typo in comment

* fix: include return values of INDCMP

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

8
.idea/kotlinc.xml generated
View File

@@ -7,13 +7,13 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards" />
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
<option name="version" value="2.2.0" />
</component>
</project>

View File

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

View File

@@ -4,8 +4,8 @@
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

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

View File

@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>

4
.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>
@@ -25,4 +26,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>

View File

@@ -61,6 +61,7 @@ What does Prog8 provide?
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- Structs and typed pointers
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- tight control over Zeropage usage
@@ -71,6 +72,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
@@ -95,7 +97,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "pet32": Commodore PET (limited support)
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64OS, ...
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)

View File

@@ -1,9 +1,10 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
plugins {
kotlin("jvm") version "2.1.20"
kotlin("jvm") version "2.2.10"
}
allprojects {
@@ -18,6 +19,7 @@ allprojects {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
jvmTarget = JvmTarget.JVM_11
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
sourceSets.all {
languageSettings {

View File

@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -7,7 +5,7 @@ plugins {
dependencies {
// should have no dependencies to other modules
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {

View File

@@ -96,12 +96,13 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *BaseDataType.entries.toTypedArray())),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
@@ -131,10 +132,15 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD)),
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, null),

View File

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

View File

@@ -27,7 +27,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: String?
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
fun initializeMemoryAreas(compilerOptions: CompilationOptions)

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

@@ -72,6 +72,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val size: Int =
when {
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isPointer -> options.compTarget.memorySize(datatype, null)
datatype.isString || datatype.isArray -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
@@ -122,6 +123,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
datatype.isNumericOrBool -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
datatype.isString -> VarAllocation(address, datatype, size)
datatype.isArray -> VarAllocation(address, datatype, size)
datatype.isPointer -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
}
}

View File

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

View File

@@ -7,12 +7,15 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -27,7 +27,7 @@ class ConfigFileTarget(
override val defaultOutputType: OutputType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: String?,
override val additionalAssemblerOptions: List<String>,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
@@ -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 {
@@ -109,8 +109,6 @@ class ConfigFileTarget(
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val assemblerOptions = assemblerOptionsStr.ifBlank { null }
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
@@ -128,7 +126,7 @@ class ConfigFileTarget(
defaultOutputType,
libraryPath,
customLauncher,
assemblerOptions,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),

View File

@@ -6,12 +6,15 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -18,7 +18,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero

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,12 +6,15 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@@ -3,16 +3,20 @@ package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@@ -70,16 +74,11 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val filename = programNameWithPath.name
if(programNameWithPath.isReadable()) {
vm.runProgram(programNameWithPath.readText(), quiet)
} else {
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
override fun isIOAddress(address: UInt): Boolean = false
@@ -88,31 +87,6 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
}

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

@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -9,12 +7,10 @@ dependencies {
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
sourceSets {

View File

@@ -13,7 +13,6 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>

View File

@@ -30,14 +30,17 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
private fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
val expressionsToFixSubtype = mutableListOf<PtExpression>()
val variablesToFixSubtype = mutableListOf<IPtVariable>()
fun prefixNamedNode(node: PtNamedNode) {
when(node) {
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
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 PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // only prefix user defined labels
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
is PtStructDecl -> node.name = "p8t_${node.name}"
}
}
@@ -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) {
@@ -62,11 +62,28 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
}
}
is PtIdentifier -> {
// check if the identifier is part of a pointer dereference (which means you cannot look it up in the symboltable because it's a struct field)
val pexpr = node.parent as? PtBinaryExpression
if(pexpr?.operator==".") {
if(pexpr.left is PtArrayIndexer) {
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
if(arrayvar.type.subType!=null) {
// don't prefix a struct field name here, we take care of that at asm generation time, which was a lot easier
//nodesToPrefix += node.parent to node.parent.children.indexOf(node)
return
} else
throw AssemblyError("can only use deref expression on struct type")
} else {
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
}
}
// normal (non pointer deref) expression
var lookupName = node.name
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
val stNode = st.lookup(lookupName) ?:
throw AssemblyError("unknown identifier $node")
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
@@ -81,8 +98,28 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
is PtStructDecl -> prefixNamedNode(node) // note: field names are not prefixed here, we take care of that at asm generation time, which was a lot easier
is PtBuiltinFunctionCall -> {
// could be a struct instance creation
if(node.name=="prog8_lib_structalloc") {
val struct = node.type.subType!!
if(struct is StStruct) {
// update link to active symboltable node
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
}
}
}
else -> { }
}
if(node is IPtVariable) {
if(node.type.subType!=null)
variablesToFixSubtype.add(node)
} else if(node is PtExpression) {
if(node.type.subType!=null)
expressionsToFixSubtype.add(node)
}
node.children.forEach { prefixSymbols(it) }
}
@@ -139,8 +176,40 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
}
}
return SymbolTableMaker(program, options).make()
val updatedSt = SymbolTableMaker(program, options).make()
fun findSubtypeReplacement(sub: ISubType): StStruct? {
if(updatedSt.lookup(sub.scopedNameString)!=null)
return null
val old = st.lookup(sub.scopedNameString)
if(old==null)
throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
val prefixed = ArrayDeque<String>()
var node: StNode = old
while(node.type!= StNodeType.GLOBAL) {
val typeChar = typePrefixChar(node.type)
prefixed.addFirst("p8${typeChar}_${node.name}")
node=node.parent
}
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
}
expressionsToFixSubtype.forEach { node ->
findSubtypeReplacement(node.type.subType!!)?.let {
node.type.subType = it
}
}
variablesToFixSubtype.forEach { node ->
findSubtypeReplacement(node.type.subType!!)?.let {
node.type.subType = it
}
}
return updatedSt
}
}
private fun prefixScopedName(name: String, type: Char): String {
@@ -175,10 +244,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, false, 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 +255,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
}
@@ -207,19 +276,34 @@ private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
}
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
var target = st.lookup(name)
val targetNt: StNodeType
val target = st.lookup(name)
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
if(target==null) {
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
target = st.lookup(name.dropLast(4))
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
val target2 = st.lookup(name.dropLast(4))
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
targetNt = target2!!.type
} else {
// if no target found, assume that the identifier is a struct field
targetNt = StNodeType.STATICVAR
}
} else {
targetNt = target.type
}
val prefixType = when(target!!.type) {
val prefixType = typePrefixChar(targetNt)
val newName = prefixScopedName(name, prefixType)
val node = PtIdentifier(newName, type, position)
node.parent = parent
return node
}
private fun typePrefixChar(targetNt: StNodeType): Char {
return when(targetNt) {
StNodeType.BLOCK -> 'b'
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
StNodeType.LABEL -> 'l'
@@ -227,12 +311,10 @@ private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
StNodeType.CONSTANT -> 'c'
StNodeType.BUILTINFUNC -> 's'
StNodeType.MEMORYSLAB -> 'v'
StNodeType.STRUCT -> 't'
StNodeType.STRUCTINSTANCE -> 'i'
else -> '?'
}
val newName = prefixScopedName(name, prefixType)
val node = PtIdentifier(newName, type, position)
node.parent = parent
return node
}
@@ -255,10 +337,17 @@ class AsmGen6502Internal (
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val pointerGen = PointerAssignmentsGen(this, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, this, pointerGen, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, assignmentAsmGen, errors)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, pointerGen, assignmentAsmGen, errors)
private val ifExpressionAsmgen = IfExpressionAsmGen(this, assignmentAsmGen, errors)
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, assignmentAsmGen, this, pointerGen, allocator)
init {
assignmentAsmGen.augmentableAsmGen = augmentableAsmGen
pointerGen.augmentableAsmGen = augmentableAsmGen
}
fun compileToAssembly(): IAssemblyProgram? {
@@ -316,9 +405,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 +419,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 +509,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 +521,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 +710,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")
}
}
@@ -647,6 +719,8 @@ class AsmGen6502Internal (
val reg = register.toString().lowercase()
val indexnum = expr.index.asConstInteger()
if (indexnum != null) {
if(indexnum > 255)
throw AssemblyError("array index $indexnum is larger than a byte ${expr.position}")
val indexValue = if(expr.splitWords)
indexnum
else
@@ -655,17 +729,20 @@ class AsmGen6502Internal (
return
}
if(!expr.index.type.isByte)
throw AssemblyError("array index $indexnum is larger than a byte ${expr.position}")
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 +751,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
@@ -733,15 +811,15 @@ class AsmGen6502Internal (
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${target.asmVarname}")
out(" lda #0 | sta ${target.asmVarname}")
else
out("stz ${target.asmVarname}")
out(" stz ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val address = target.memory!!.address.asConstInteger()
if(address!=null) {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${address.toHex()}")
out(" lda #0 | sta ${address.toHex()}")
else
out(" stz ${address.toHex()}")
return
@@ -750,7 +828,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 -> { }
@@ -760,6 +838,7 @@ class AsmGen6502Internal (
assignExpressionToRegister(value, RegisterOrPair.A)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, target.datatype.isSigned, false)
}
target.datatype.isPointer -> TODO("assign expression to pointer ${target.position}")
target.datatype.isWord || target.datatype.isPassByRef -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
translateNormalAssignment(
@@ -812,7 +891,7 @@ class AsmGen6502Internal (
when {
iterations == 0 -> {}
iterations == 1 -> translate(stmt.statements)
iterations<0 || iterations>65536 -> throw AssemblyError("invalid number of iterations")
iterations !in 0..65536 -> throw AssemblyError("invalid number of iterations")
iterations <= 256 -> repeatByteCount(iterations, stmt)
else -> repeatWordCount(iterations, stmt)
}
@@ -854,7 +933,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 +953,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 +976,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 +994,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 +1009,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 +1022,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 +1052,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 +1088,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 +1140,26 @@ $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 ?: 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,18 +1171,20 @@ $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!!)
val returnDt = sub.signature.returns.single()
if (returnDt.isNumericOrBool || returnDt.isPointer) {
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)
addrofValue.add(returnvalue as PtIdentifier)
val addrOfDt = returnvalue.type.typeForAddressOf(false)
val addrofValue = PtAddressOf(addrOfDt, false, returnvalue.position)
addrofValue.add(returnvalue)
addrofValue.parent = ret.parent
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnRegs.single().first.registerOrPair!!, false)
}
@@ -1239,11 +1311,11 @@ $repeatLabel""")
return null
val leftDt = left.type
val rightDt = right.type
if(leftDt.isUnsignedWord && rightDt.isUnsignedByte)
if((leftDt.isUnsignedWord || leftDt.isPointer) && rightDt.isUnsignedByte)
return Pair(left, right)
if(leftDt.isUnsignedByte && rightDt.isUnsignedWord)
return Pair(right, left)
if(leftDt.isUnsignedWord && rightDt.isUnsignedWord) {
if((leftDt.isUnsignedWord || leftDt.isPointer) && rightDt.isUnsignedWord) {
// could be that the index was a constant numeric byte but converted to word, check that
val constIdx = right as? PtNumber
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
@@ -1279,9 +1351,7 @@ $repeatLabel""")
}
if(addressExpr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr, false)
if (ptrAndIndex == null) return false
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr) ?: return false
if(write) {
// WRITING TO pointer + offset
@@ -1291,8 +1361,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 +1403,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
}
}
@@ -1369,9 +1443,7 @@ $repeatLabel""")
}
else if(addressExpr.operator=="-") {
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr, true)
if (ptrAndIndex == null) return false
val ptrAndIndex = pointerViaIndexRegisterPossible(addressExpr, true) ?: return false
if(write) {
// WRITING TO pointer - offset
@@ -1381,8 +1453,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 +1486,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 +1520,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.scopedName==name)
return param
}
}
return null
}
internal fun assignByteOperandsToAAndVar(left: PtExpression, right: PtExpression, rightVarName: String) {
@@ -1549,6 +1633,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 +1698,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 +1714,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 +1759,7 @@ $repeatLabel""")
}
internal fun assignConditionValueToRegisterAndTest(condition: PtExpression) {
assignExpressionToRegister(condition, RegisterOrPair.A, false)
assignExpressionToRegister(condition, RegisterOrPair.A)
when(condition) {
is PtNumber,
is PtBool,

View File

@@ -408,7 +408,7 @@ private fun optimizeStoreLoadSame(
// a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used)
val loadinstruction = second.substring(0, 3)
val loadinstruction = second.take(3)
lines[0].value.trimStart().startsWith(loadinstruction)
}
else {
@@ -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

@@ -28,6 +28,14 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
command.addAll(options.compTarget.additionalAssemblerOptions)
command.addAll(listOf("--output", program.toString(), assembly.toString()))
return command
}
when(options.output) {
OutputType.PRG -> {
// CBM machines .prg generation.
@@ -47,8 +55,7 @@ internal class AssemblyProgram(
command.add("--list=$listFile")
}
command.addAll(listOf("--output", prgFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
if(!options.quiet)
println("\nCreating prg for target ${compTarget.name}.")
}
@@ -69,8 +76,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", xexFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
if(!options.quiet)
println("\nCreating xex for target ${compTarget.name}.")
}
@@ -90,8 +96,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
if(!options.quiet)
println("\nCreating raw binary for target ${compTarget.name}.")
}
@@ -122,14 +127,10 @@ internal class AssemblyProgram(
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
}
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
}
if(options.compTarget.additionalAssemblerOptions!=null)
assemblerCommand.add(options.compTarget.additionalAssemblerOptions!!)
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()

View File

@@ -1,5 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -46,6 +47,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peekbool" -> funcPeekBool(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokef" -> funcPokeF(fcall)
@@ -53,19 +55,21 @@ 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)
asmgen.out(" pla")
}
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"pokebool" -> funcPokeBool(fcall)
"rsave" -> funcRsave()
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"callfar2" -> funcCallFar2(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_lib_structalloc" -> funcStructAlloc(fcall, discardResult, resultRegister)
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister)
@@ -380,8 +384,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(fcall.position)
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
@@ -390,6 +395,21 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcStructAlloc(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult)
throw AssemblyError("should not discard result of struct allocation at $fcall")
// ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it
val slabname = SymbolTable.labelnameForStructInstance(fcall)
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position))
addressOf.parent = fcall
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf)
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
val assign = AsmAssignment(src, listOf(target), program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcSqrt(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
when(fcall.args[0].type.base) {
@@ -415,8 +435,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
@@ -438,8 +461,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UWORD -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
else
@@ -465,7 +490,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" ror ${varname},x")
}
is PtMemoryByte -> {
@@ -498,7 +526,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
else
@@ -521,8 +551,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.UBYTE -> {
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
@@ -545,7 +577,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (what) {
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
else
@@ -571,7 +605,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" rol ${varname},x")
}
is PtMemoryByte -> {
@@ -604,7 +640,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(!what.index.isSimple()) asmgen.out(" php") // save Carry
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
val varname = asmgen.asmVariableName(what.variable)
if(what.variable==null)
TODO("support for ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
else
@@ -633,7 +671,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
val arrayVariable = indexer.variable ?: TODO("support for ptr indexing ${indexer.position}")
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.arrayFor(BaseDataType.UBYTE, false), arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
@@ -676,7 +714,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 +777,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
@@ -754,6 +792,58 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPokeBool(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr")
return
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
if (asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" sta ($varname)")
else
asmgen.out(" ldy #0 | sta ($varname),y")
return
}
}
is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
// can do ZP,Y indexing
val varname = asmgen.asmVariableName(pointer)
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
asmgen.out(" sta ($varname),y")
return
}
}
else -> { /* fall through */ }
}
// fall through method:
if(fcall.args[1].isSimple()) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A)
} else {
asmgen.pushCpuStack(BaseDataType.UBYTE, fcall.args[1])
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.restoreRegisterStack(CpuRegister.A, false)
}
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" sta (P8ZP_SCRATCH_W1)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W1),y")
}
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
@@ -815,7 +905,49 @@ 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(), register=resultRegister, position=fcall.position))
}
}
private fun funcPeekBool(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
fun fallback() {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peek")
}
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr")
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
if (asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda ($varname)")
else
asmgen.out(" ldy #0 | lda ($varname),y")
} else fallback()
}
is PtBinaryExpression -> {
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
// can do ZP,Y indexing
val varname = asmgen.asmVariableName(pointer)
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.out(" lda ($varname),y")
} else fallback()
}
else -> fallback()
}
when(resultRegister ?: RegisterOrPair.A) {
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" tax")
RegisterOrPair.Y -> asmgen.out(" tay")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().lowercase()}")
else -> throw AssemblyError("invalid reg")
}
}
@@ -1112,7 +1244,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 +1263,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 +1347,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 +1415,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
fun getSourceForFloat(value: PtExpression): AsmAssignSource {
return when (value) {
is PtIdentifier -> {
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), false, value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -1290,7 +1429,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.pointer(BaseDataType.FLOAT), false, value.position)
addr.add(variable)
addr.parent = call
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT)
@@ -1310,7 +1449,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -1328,7 +1467,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)

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

@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we consider them NOT to be optimized into (possibly different) CPU registers.
// Just load them in whatever the register spec says.
return when (params.size) {
1 -> params[0].type.isIntegerOrBool && params[0].register == null
1 -> params[0].register == null && (params[0].type.isIntegerOrBool || params[0].type.isPointer)
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
else -> false
}
@@ -36,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
return
}
@@ -139,15 +139,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
else if(sub is PtSub) {
if(optimizeIntArgsViaCpuRegisters(sub.parameters)) {
val parameters = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(optimizeIntArgsViaCpuRegisters(parameters)) {
// Note that if the args fit into cpu registers, we don't concern ourselves here
// if they should be put into regular subroutine parameter variables, or the R0-R15 register variables.
// That is now up to the subroutine itself.
useCpuRegistersForArgs(call.args, sub)
} else {
// arguments via variables
val paramValues = sub.parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { it.first.register == null }
val paramValues = parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) }
if (normalParams.isNotEmpty()) {
for (p in normalParams)
argumentViaVariable(sub, p.first, p.second)
@@ -166,7 +167,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun useCpuRegistersForArgs(args: List<PtExpression>, sub: PtSub) {
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
when(params.size) {
1 -> {
val register = if (params[0].type.isByteOrBool) RegisterOrPair.A else RegisterOrPair.AY
@@ -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 {
@@ -332,14 +333,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
val scope = value.definingISub()
val target: AsmAssignTarget =
if(parameter.value.type.isByte && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
if(parameter.value.type.isByte && register.isWord())
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
else {
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)

View File

@@ -6,11 +6,13 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.AssignmentAsmGen
import prog8.codegen.cpu6502.assignment.PointerAssignmentsGen
import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class IfElseAsmGen(private val program: PtProgram,
private val st: SymbolTable,
private val asmgen: AsmGen6502Internal,
private val pointergen: PointerAssignmentsGen,
private val assignmentAsmGen: AssignmentAsmGen,
private val errors: IErrorReporter) {
@@ -38,13 +40,12 @@ internal class IfElseAsmGen(private val program: PtProgram,
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
return
}
val rightDt = compareCond.right.type
return when {
rightDt.isByteOrBool -> translateIfByte(stmt, jumpAfterIf)
rightDt.isWord -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isWord || rightDt.isPointer -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
else -> throw AssemblyError("weird dt")
}
@@ -64,6 +65,16 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
val dereference = stmt.condition as? PtPointerDeref
if(dereference!=null) {
val zpPtrVar = pointergen.deref(dereference)
pointergen.loadIndirectByte(zpPtrVar)
return if (jumpAfterIf != null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
}
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
}
@@ -87,6 +98,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
@@ -94,6 +106,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
@@ -107,6 +120,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
@@ -114,6 +128,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
@@ -146,6 +161,93 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodiesSignedByte(elseConditional: String, value: PtExpression, stmt: PtIfElse) {
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
val afterIfLabel = asmgen.makeLabel("afterif")
asmgen.cmpAwithByteValue(value, true)
if(stmt.hasElse()) {
// if and else blocks
val elseLabel = asmgen.makeLabel("else")
branchElse(elseLabel)
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
// no else block
branchElse(afterIfLabel)
asmgen.translate(stmt.ifScope)
}
asmgen.out(afterIfLabel)
}
private fun translateJumpElseBodiesSignedByte(elseConditional: String, value: PtExpression, jump: PtJump, elseBlock: PtNodeGroup) {
fun branchTarget(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
var target = asmgen.getJumpTarget(jump, false)
asmgen.cmpAwithByteValue(value, true)
if(target.indirect) {
branchElse("+")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
branchTarget(target.asmLabel)
}
asmgen.translate(elseBlock)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@@ -154,7 +256,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 +273,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 +311,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", stmt)
}
"<" -> translateByteLess(stmt, signed, jumpAfterIf)
"<=" -> {
// X<=Y -> Y>=X (reverse of >=)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.left, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
"<=" -> translateByteLessEqual(stmt, signed, jumpAfterIf)
">" -> translateByteGreater(stmt, signed, jumpAfterIf)
">=" -> {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
">=" -> translateByteGreaterEqual(stmt, signed, jumpAfterIf)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
@@ -286,7 +358,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 +379,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 +426,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 +449,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 +476,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun translateByteLess(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcc", "bcs", jumpAfterIf, stmt.elseScope)
else
@@ -416,25 +490,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 +545,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 +558,23 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteGreaterEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -535,7 +644,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 +672,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 +700,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 +726,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 +778,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 +806,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 +833,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 +857,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 +897,9 @@ _jump jmp (${target.asmLabel})
private fun loadAndCmp0MSB(value: PtExpression) {
when(value) {
is PtArrayIndexer -> {
val varname = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varname = asmgen.asmVariableName(value.variable!!)
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords)
asmgen.out(" lda ${varname}_msb,y")
@@ -800,8 +915,8 @@ _jump jmp (${target.asmLabel})
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(value.identifier)
if(value.identifier.type.isSplitWordArray) {
var varname = asmgen.asmVariableName(value.identifier!!)
if(value.identifier!!.type.isSplitWordArray) {
varname += if(value.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
@@ -829,7 +944,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 +973,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 +994,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@@ -908,7 +1026,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 +1055,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 +1092,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 +1121,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 +1143,10 @@ _jump jmp (${target.asmLabel})
if(value is PtIdentifier)
return compareLsbMsb(value.name, value.name+"+1")
if(value is PtArrayIndexer) {
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val constIndex = value.index.asConstInteger()
val varname = asmgen.asmVariableName(value.variable)
val varname = asmgen.asmVariableName(value.variable!!)
if(constIndex!=null) {
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb+$constIndex", "${varname}_msb+$constIndex")
@@ -1053,7 +1175,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 +1204,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 +1259,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if(constIndex!=null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "bne", "beq")
}
@@ -1157,7 +1282,9 @@ _jump jmp (${target.asmLabel})
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if (constIndex != null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.variable==null)
TODO("support for ptr indexing ${value.position}")
val varName = asmgen.asmVariableName(value.variable!!)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "beq", "bne")
}
@@ -1196,7 +1323,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 +1349,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 +1376,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 +1402,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 +1431,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 +1461,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 +1491,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 +1521,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 +1554,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 +1584,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 +1613,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 +1643,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 +1665,10 @@ _jump jmp (${target.asmLabel})
fun translateEqualsArray(left: PtArrayIndexer, right: PtExpression) {
val constIndex = left.index.asConstInteger()
if(constIndex!=null) {
if(left.variable==null)
TODO("support for ptr indexing ${left.position}")
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varName = asmgen.asmVariableName(left.variable)
val varName = asmgen.asmVariableName(left.variable!!)
if(left.splitWords) {
return if(notEquals)
translateAYNotEquals("${varName}_lsb+$constIndex", "${varName}_msb+$constIndex")
@@ -1596,13 +1731,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYNotEquals("#<$varname", "#>$varname")
@@ -1648,13 +1783,13 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
val varname = if(left.identifier!!.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier!!.name+"_msb" else left.identifier!!.name+"_lsb"
} else {
left.identifier.name
left.identifier!!.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYEquals("#<$varname", "#>$varname")

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

@@ -44,8 +44,8 @@ internal class ProgramAndVarsGen(
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
structInstances2asm()
memorySlabs()
tempVars()
footer()
}
}
@@ -208,40 +208,20 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section slabs_BSS")
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("prog8_slabs\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
}
asmgen.out("\t.bend\n .send slabs_BSS")
asmgen.out("\t.bend\n .send BSS_SLABS")
}
}
private fun tempVars() {
asmgen.out("; expression temp vars\n .section BSS")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.BYTE -> asmgen.out("$name .char ?")
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.WORD -> asmgen.out("$name .sint ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS")
}
private fun footer() {
asmgen.out(" .dsection STRUCTINSTANCES\n")
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
@@ -297,14 +277,14 @@ internal class ProgramAndVarsGen(
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(relocateBssVars) {
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
if(relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
@@ -313,12 +293,12 @@ internal class ProgramAndVarsGen(
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
if(relocateBssSlabs) {
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
}
}
@@ -351,9 +331,10 @@ internal class ProgramAndVarsGen(
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
val constvalue = assign.value as? PtNumber
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
// the other variables that should be set to zero are done so as part of the BSS section.
// 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 +351,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,11 +365,93 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
private fun asmTypeString(dt: DataType): String {
return when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun structInstances2asm() {
fun initValues(instance: StStructInstance): List<String> {
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
if(field.first.isFloat) {
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
} else {
when {
value.number!=null -> {
if(field.first.isPointer)
"$"+value.number!!.toInt().toString(16)
else if(field.first.isInteger)
value.number!!.toInt().toString()
else
value.number.toString()
}
value.addressOfSymbol!=null -> value.addressOfSymbol!!
value.boolean!=null -> if(value.boolean==true) "1" else "0"
else -> throw AssemblyError("weird struct initial value $value")
}
}
}
}
asmgen.out("; struct types")
symboltable.allStructInstances.distinctBy { it.structName }.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${it.structName} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first
val varname = "f${index}"
val type = when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isFloat -> ".byte" // TODO check that float bytes are passed as an array parameter
else -> throw AssemblyError("weird dt")
}
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
}
asmgen.out(" .endstruct\n")
}
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
if(field.first.isFloat) {
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
"[${floatbytes.joinToString(",")}]"
}
else "?"
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") }
asmgen.out(" .send STRUCTINSTANCES\n")
}
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
return // subroutine gets inlined at call site.
@@ -438,7 +501,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 +519,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 +555,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 +571,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 +630,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 +699,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 +776,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 +833,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 +841,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 +943,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())
@@ -926,7 +1008,7 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}

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(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)

View File

@@ -20,6 +20,8 @@ internal class AnyExprAsmGen(
private val asmgen: AsmGen6502Internal
) {
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
if(expr.operator==".")
throw AssemblyError("pointer deref expression should have been handled elsewhere ${expr.position}")
when {
expr.type.isByteOrBool -> {
if(expr.left.type.isByteOrBool && expr.right.type.isByteOrBool)
@@ -46,6 +48,12 @@ internal class AnyExprAsmGen(
}
return assignFloatBinExpr(expr, assign)
}
expr.type.isPointer -> {
require((expr.left.type.isPointer || expr.left.type.isUnsignedWord) && (expr.right.type.isPointer || expr.right.type.isUnsignedWord)) {
"both operands must be pointers or uwords"
}
throw AssemblyError("expression should have been handled otherwise: pointer ${expr.operator} at ${expr.position}")
}
else -> throw AssemblyError("weird expression type in assignment")
}
}
@@ -53,7 +61,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 +69,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 +84,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 +92,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 +100,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

@@ -6,11 +6,12 @@ import prog8.codegen.cpu6502.AsmGen6502Internal
internal enum class TargetStorageKind {
VARIABLE,
VARIABLE, // non-pointer variable
ARRAY,
MEMORY,
REGISTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
POINTER, // wherever the pointer variable points to
VOID // assign nothing - used in multi-value assigns for void placeholders
}
internal enum class SourceStorageKind {
@@ -32,6 +33,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val pointer: PtPointerDeref? = null,
val origAstTarget: PtAssignTarget? = null
)
{
@@ -39,13 +41,24 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
}
init {
if(register!=null && !datatype.isNumericOrBool)
throw AssemblyError("must be numeric type")
if(kind==TargetStorageKind.REGISTER)
require(register!=null)
else
require(register==null)
if(kind==TargetStorageKind.POINTER)
require(pointer!=null)
if(pointer!=null)
require(kind==TargetStorageKind.POINTER)
}
companion object {
@@ -77,6 +90,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
@@ -134,11 +148,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
left is PtIdentifier && left.name==scopedName
}
TargetStorageKind.ARRAY -> {
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords && (left.pointerderef==null && array.pointerderef==null || left.pointerderef!! isSameAs array.pointerderef!!)
}
TargetStorageKind.MEMORY -> {
left isSameAs memory!!
}
TargetStorageKind.POINTER -> {
TODO("is pointer deref target same as expression? ${this.position}")
}
TargetStorageKind.REGISTER -> false
TargetStorageKind.VOID -> false
}
@@ -160,8 +177,11 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
companion object {
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
@@ -203,7 +223,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.returns.size>1)
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second

View File

@@ -9,6 +9,7 @@ import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen6502Internal,
private val ptrgen: PointerAssignmentsGen,
private val allocator: VariableAllocator
) {
fun translate(assign: AsmAugmentedAssignment, scope: IPtSubroutine?) {
@@ -84,22 +85,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isWord -> {
target.datatype.isWord || target.datatype.isPointer -> {
val block = target.origAstTarget?.definingBlock()
val targetDt = if(target.datatype.isWord) target.datatype else DataType.UWORD // pointers themselves that get a new value are just treated as UWORD variables
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.boolean!!.asInt(), block)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!, block)
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationWordWithLiteralval(target.asmVarname, targetDt, operator, value.boolean!!.asInt(), block)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, targetDt, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, targetDt, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, targetDt, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, targetDt, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression, block)
inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.expression, block)
}
else {
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!, block)
inplacemodificationWordWithValue(target.asmVarname, targetDt, operator, value.expression!!, block)
}
}
}
@@ -170,7 +172,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 +207,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
@@ -224,7 +226,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.ARRAY -> {
val indexNum = target.array!!.index as? PtNumber
val deref = target.array!!.pointerderef
if(deref!=null) {
TODO("inplace modification array indexed pointer deref ${target.position}")
return
}
val targetArrayVar = target.array.variable!!
val indexNum = target.array.index as? PtNumber
if (indexNum!=null) {
val index = indexNum.number.toInt()
if(target.array.splitWords) {
@@ -301,7 +309,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isPointer -> TODO("inplace modification of pointer array ${target.position}")
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
@@ -320,7 +328,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" lda ${targetArrayVar.name},y")
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
inplacemodificationRegisterAwithVariable(operator, "#${value.boolean!!.asInt()}", target.datatype.isSigned)
@@ -356,7 +364,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
@@ -366,7 +374,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda $tempVar")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
asmgen.out(" sta ${targetArrayVar.name},y")
}
target.datatype.isWord -> {
@@ -377,11 +385,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
asmgen.out(" lda ${targetArrayVar.name}_lsb,y")
asmgen.out(" ldx ${targetArrayVar.name}_msb,y")
} else {
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
asmgen.out(" lda ${targetArrayVar.name},y")
asmgen.out(" ldx ${targetArrayVar.name}+1,y")
}
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
@@ -439,7 +447,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
@@ -450,14 +458,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(target.array.splitWords)
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
asmgen.out(" sta ${targetArrayVar.name}_lsb,y | txa | sta ${targetArrayVar.name}_msb,y")
else
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
asmgen.out(" sta ${targetArrayVar.name},y | txa | sta ${targetArrayVar.name}+1,y")
}
target.datatype.isFloat -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
@@ -504,18 +512,24 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
pla ; restore array ptr lsb
jsr floats.copy_float""")
}
target.datatype.isPointer -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
}
}
TargetStorageKind.POINTER -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
val arrayvar = asmgen.asmVariableName(array.variable)
val arrayVar = array.variable
if(arrayVar==null) {
TODO("indexed inc/dec on pointer ${array.position}")
return false
}
val arrayvar = asmgen.asmVariableName(arrayVar)
when {
array.type.isByte -> {
asmgen.loadScaledArrayIndexIntoRegister(array, CpuRegister.X)
@@ -583,7 +597,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 +649,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()
@@ -884,7 +898,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun tryInplaceModifyWithRemovedRedundantCast(value: PtTypeCast, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
if (target.datatype == value.type || (target.datatype.isPointer && value.type.isWord)) {
val childDt = value.value.type
if (!value.type.isFloat && (value.type.equalsSize(childDt) || value.type.largerSizeThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
@@ -910,7 +924,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@@ -972,7 +986,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tinc ${'$'}ffff\t; modified")
asmgen.out($$"+\tinc $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@@ -992,7 +1006,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.out("+\tdec ${'$'}ffff\t; modified")
asmgen.out($$"+\tdec $ffff\t; modified")
}
} else {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
@@ -1019,7 +1033,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 +1139,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 +1211,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 +1384,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 +1555,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@@ -1921,18 +1942,16 @@ $shortcutLabel:""")
asmgen.out(" lda #0 | sta $lsb")
}
value==7 -> {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
asmgen.out("""
; shift left 7
lsr $msb
php ; save carry
lda $lsb
ror a
sta $msb
lda #0
sta $lsb
plp ; restore carry
ror $msb
ror $lsb""")
ror a
sta $lsb""")
}
value>3 -> asmgen.out("""
ldy #$value
@@ -2258,7 +2277,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType, block: PtBlock?) {
require(dt.isWord)
require(valueDt.isInteger)
require(valueDt.isInteger || valueDt.isPointer)
when {
valueDt.isByte -> {
// the other variable is a BYTE type so optimize for that
@@ -2468,7 +2487,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 +2759,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 +2909,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 +2924,12 @@ $shortcutLabel:""")
"-" -> {
if(value.index.type.isByte) {
// it's an array indexed by a byte so we can use sbc array,y
val arrayname = value.variable.name
val valueVar = value.variable
if(valueVar==null) {
TODO("inplace modification on pointer ${value.position}")
return
}
val arrayname = valueVar.name
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
asmgen.out("""

File diff suppressed because it is too large Load Diff

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,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -11,7 +9,7 @@ dependencies {
implementation(project(":codeGenIntermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {

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,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -11,12 +9,10 @@ dependencies {
implementation(project(":intermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
sourceSets {

View File

@@ -13,9 +13,8 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>

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.*
@@ -26,9 +28,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokebool" -> funcPoke(call, IRDataType.BYTE)
"pokebowl" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
@@ -45,6 +50,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -78,7 +85,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 +97,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 +148,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 +244,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 +282,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 +500,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.BYTE, resultReg, -1)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcStructAlloc(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname)
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -548,7 +559,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
val variable = arr.variable.name
if(arr.variable==null)
TODO("support for ptr indexing ${arr.position}")
val variable = arr.variable!!.name
if(arr.splitWords) {
result += IRCodeChunk(null, null).also {
when(opcodeMemAndReg.first) {
@@ -617,7 +630,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(target.variable==null)
TODO("support for ptr indexing ${target.position}")
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
@@ -651,6 +666,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
else {
val targetVariable = target.variable ?: TODO("support for ptr indexing ${target.position}")
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
@@ -659,17 +676,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
} else {
@@ -680,17 +697,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
}
}
}

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,77 @@ 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 if(identifier.type.isPointer) {
// apply pointer arithmetic for the array indexing
val eltSize = if(identifier.type.sub!=null)
codeGen.program.memsizer.memorySize(identifier.type.sub!!)
else
identifier.type.subType!!.memsize(codeGen.program.memsizer)
result += IRCodeChunk(null, null).also {
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name), null)
if (eltSize > 1) {
it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = resultRegister, reg2 = indexWordReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
// regular array indexing
val eltSize = codeGen.program.memsizer.memorySize(identifier.type, 1)
result += IRCodeChunk(null, null).also {
loadAddressOfArrayLabel(resultRegister)
if(eltSize>1 && !expr.identifier.type.isSplitWordArray) {
it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1=indexWordReg, immediate = eltSize)
if (eltSize > 1 && !identifier.type.isSplitWordArray) {
it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = resultRegister, reg2 = indexWordReg)
}
}
} else {
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else if(expr.identifier!=null ) {
val resultRegister = codeGen.registers.next(vmDt)
loadAddressOfArrayLabel(resultRegister)
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
} else {
require(vmDt==IRDataType.WORD)
val pointerTr = translateExpression(expr.dereference!!.startpointer)
result += pointerTr.chunks
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg)
result += instructions
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset.toInt()), null)
return ExpressionCodeResult(result, vmDt, pointerTr.resultReg, -1)
}
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
private fun translate(mem: PtMemoryByte): ExpressionCodeResult {
@@ -219,31 +339,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 +410,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 +420,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 +433,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 +446,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 +463,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 +513,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 +523,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 +618,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)
@@ -456,7 +629,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=actualResultReg2, immediate = 1)
}
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.UBYTE -> {
@@ -472,7 +645,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.BYTE -> {
@@ -488,7 +661,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.UWORD -> {
@@ -510,7 +683,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
BaseDataType.POINTER -> {
actualResultReg2 = tr.resultReg
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.WORD -> {
@@ -532,7 +708,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.FLOAT -> {
@@ -550,49 +726,61 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
}
else -> throw AssemblyError("weird cast value type")
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
else -> throw AssemblyError("weird cast type")
BaseDataType.POINTER -> {
require(valueDt.isUnsignedWord || valueDt.isPointer)
actualResultReg2 = tr.resultReg
// no further conversion required, pointers are all just uwords
}
BaseDataType.ARRAY_POINTER -> {
TODO("typecast to array of pointers $valueDt -> ${cast.type}")
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
return ExpressionCodeResult(result, irType(cast.type), actualResultReg2, actualResultFpReg2)
}
private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult {
val vmDt = irType(binExpr.left.type)
val signed = binExpr.left.type.isSigned
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
"!=" -> operatorEquals(binExpr, vmDt, true)
"<" -> operatorLessThan(binExpr, vmDt, signed, false)
">" -> operatorGreaterThan(binExpr, vmDt, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, signed, true)
else -> throw AssemblyError("weird operator ${binExpr.operator}")
if(binExpr.operator==".") {
return operatorDereference(binExpr) // eww, nasty, would rather not have any such expressions anymore
} else {
val vmDt = irType(binExpr.left.type)
return when (binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
"!=" -> operatorEquals(binExpr, vmDt, true)
"<" -> operatorLessThan(binExpr, vmDt, signed, false)
">" -> operatorGreaterThan(binExpr, vmDt, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, signed, true)
else -> throw AssemblyError("weird operator ${binExpr.operator}")
}
}
}
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
if(callTarget.scopedNameString in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
return translateStackFunctions(fcall, callTarget)
}
when(callTarget.scopedName) {
when(callTarget.scopedNameString) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLC), null)
@@ -618,7 +806,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 +829,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 +857,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 +895,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 +960,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 +972,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 +1005,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 +1240,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 +1255,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 +1415,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 +1431,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 +1443,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 +1494,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 +1502,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 +1632,117 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDereference(binExpr: PtBinaryExpression): ExpressionCodeResult {
// the only case we support here is: a.b.c[i] . value
val left = binExpr.left as? PtArrayIndexer
val right = binExpr.right as? PtIdentifier
require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"}
val result = mutableListOf<IRCodeChunkBase>()
val field: Pair<DataType, UInt>
val pointerReg: Int
var extraFieldOffset = 0
if(left.type.isStructInstance) {
// indexing on a pointer directly
// fetch pointer address, determine struct and field, add index * structsize
if(left.variable!=null) {
val pointerTr = translateExpression(left.variable!!)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
} else if(left.pointerderef!=null) {
TODO("get pointer from deref $left")
} else {
throw AssemblyError("weird arrayindexer $left")
}
val struct = left.type.subType!! as StStruct
val constindex = left.index as? PtNumber
if(constindex!=null) {
extraFieldOffset = struct.size.toInt() * constindex.number.toInt()
} else {
val (chunks, indexReg) = codeGen.loadIndexReg(left.index, struct.size.toInt(), true, false)
result += chunks
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerReg, reg2 = indexReg), null)
}
field = struct.getField(right.name, codeGen.program.memsizer)
} else {
// indexing on an array with pointers
// fetch the pointer from the array, determine the struct & field
val indexedTr = translateExpression(left)
result += indexedTr.chunks
pointerReg = indexedTr.resultReg
val struct = left.type.dereference().subType as? StStruct
require(indexedTr.dt == IRDataType.WORD && struct != null)
field = struct.getField(right.name, codeGen.program.memsizer)
}
// add field offset to pointer and load the value into the result register
val fieldVmDt = irType(field.first)
var resultFpReg = -1
var resultReg = -1
if (fieldVmDt == IRDataType.FLOAT)
resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
else
resultReg = codeGen.registers.next(fieldVmDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = field.second.toInt() + extraFieldOffset)
it += if (fieldVmDt == IRDataType.FLOAT)
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
else
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
}
return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg)
}
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, 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,16 @@ class IRCodeGen(
if(variable.uninitialized && variable.parent.type==StNodeType.BLOCK) {
val block = variable.parent.astNode as PtBlock
val initialization = (block.children.firstOrNull {
it is PtAssignment && it.target.identifier?.name==variable.scopedName
it is PtAssignment && it.isVarInitializer && it.target.identifier?.name==variable.scopedNameString
} as PtAssignment?)
val initValue = initialization?.value
when(initValue){
when(val initValue = initialization?.value){
is PtBool -> {
require(initValue.asInt()!=0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.asInt().toDouble())
initsToRemove += block to initialization
}
is PtNumber -> {
require(initValue.number!=0.0 || variable.zpwish!=ZeropageWish.NOT_IN_ZEROPAGE) { "non-zp variable should not be initialized with 0, it will already be zeroed as part of BSS clear, initializer=$initialization" }
variable.setOnetimeInitNumeric(initValue.number)
initsToRemove += block to initialization
}
@@ -101,6 +105,15 @@ class IRCodeGen(
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
is PtPointerDeref -> require('.' in node.startpointer.name) { "node $node name is not scoped: ${node.startpointer.name}" }
is PtIdentifier -> {
if('.' !in node.name) {
// there is 1 case where the identifier is not scoped: if it's the value field name after a pointer array indexing.
val expr = node.parent as? PtBinaryExpression
if (expr?.operator != "." || expr.right !== node || expr.left !is PtArrayIndexer || (!expr.left.type.isPointer && !expr.left.type.isStructInstance))
require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
}
}
else -> { /* node has no name or is ok to have no dots in the name */ }
}
node.children.forEach { verifyPtNode(it) }
@@ -194,10 +207,7 @@ class IRCodeGen(
old.fpReg1,
old.fpReg2,
immediate = immediateValue,
null,
address = addressValue,
null,
null
address = addressValue
)
}
}
@@ -256,9 +266,11 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node $node")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened $node")
is PtStructDecl -> emptyList()
is PtSubSignature -> emptyList()
else -> TODO("missing codegen for $node")
}
@@ -420,35 +432,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 +489,12 @@ class IRCodeGen(
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val elementDt = irType(iterable.type.elementType())
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@@ -480,9 +502,9 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
@@ -491,10 +513,9 @@ class IRCodeGen(
result += jumpChunk
result += IRCodeChunk(endLabel, null)
}
iterable.type.isSplitWordArray -> {
iterable.type.isSplitWordArray || iterable.type.isPointerArray -> {
// iterate over lsb/msb split word array
val elementDt = iterable.type.elementType()
if(!elementDt.isWord)
if(elementDt!=IRDataType.WORD)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@@ -504,7 +525,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
@@ -548,7 +569,7 @@ class IRCodeGen(
val step = iterable.step.number.toInt()
if (step==0)
throw AssemblyError("step 0")
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@@ -634,7 +655,7 @@ class IRCodeGen(
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
val loopLabel = createLabelName()
require(forLoop.variable.name == loopvar.scopedName)
require(forLoop.variable.name == loopvar.scopedNameString)
val loopvarSymbol = forLoop.variable.name
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
@@ -771,7 +792,7 @@ class IRCodeGen(
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
@@ -789,38 +810,40 @@ class IRCodeGen(
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
}
return code
}
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
internal fun multiplyByConst(dt: DataType, reg: Int, factor: Int): IRCodeChunk {
val irdt = irType(dt)
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
code += IRInstruction(Opcode.LSL, irdt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
code += IRInstruction(Opcode.LSLN, irdt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = factor)
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
IRInstruction(opcode, irdt, reg1=reg, immediate = factor)
}
}
return code
}
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
internal fun multiplyByConstInplace(dt: IRDataType, signed: Boolean, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
@@ -850,10 +873,11 @@ class IRCodeGen(
else {
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
@@ -1070,7 +1094,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 +1677,7 @@ class IRCodeGen(
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtTypeCast -> {
require(cond.type.isBool && cond.value.type.isNumeric)
require(cond.type.isBool && (cond.value.type.isNumeric || cond.value.type.isPointer))
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
@@ -1669,6 +1693,9 @@ class IRCodeGen(
is PtBinaryExpression -> {
translateBinExpr(cond)
}
is PtPointerDeref -> {
translateSimple(cond, Opcode.BSTEQ, false)
}
else -> throw AssemblyError("weird if condition ${ifElse.condition}")
}
return result
@@ -1743,9 +1770,7 @@ class IRCodeGen(
private fun isIndirectJump(jump: PtJump): Boolean {
if(jump.target.asConstInteger()!=null)
return false
val identifier = jump.target as? PtIdentifier
if(identifier==null)
return true
val identifier = jump.target as? PtIdentifier ?: return true
val symbol = symbolTable.lookup(identifier.name)
return symbol?.type==StNodeType.MEMVAR || symbol?.type==StNodeType.STATICVAR
}
@@ -1836,7 +1861,7 @@ class IRCodeGen(
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
is PtAlign -> TODO("ir support for inline %align")
is PtSub -> {
val sub = IRSubroutine(child.name, translate(child.parameters), child.returns, child.position)
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
for (subchild in child.children) {
translateNode(subchild).forEach { sub += it }
}
@@ -1885,21 +1910,22 @@ class IRCodeGen(
}
}
}
is PtStructDecl -> { /* do nothing, should be found in the symbol table */ }
else -> TODO("weird block child node $child")
}
}
return irBlock
}
private fun translate(parameters: List<PtSubroutineParameter>): List<IRSubroutine.IRParam> {
private fun translateParameters(parameters: List<PtNode>): List<IRSubroutine.IRParam> {
val result = mutableListOf<IRSubroutine.IRParam>()
parameters.forEach {
it as PtSubroutineParameter
if(it.register==null) {
val flattenedName = it.definingISub()!!.name + "." + it.name
if (symbolTable.lookup(flattenedName) == null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
result += IRSubroutine.IRParam(flattenedName, orig.dt)
require('.' in it.name) { "even parameter names should have been made fully scoped by now" }
val orig = symbolTable.lookup(it.name) as? StStaticVariable
?: TODO("fix missing lookup for: ${it.name} parameter")
result += IRSubroutine.IRParam(it.name, orig.dt)
} else {
val reg = it.register
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
@@ -1924,7 +1950,7 @@ class IRCodeGen(
internal fun isOne(expression: PtExpression): Boolean = (expression as? PtNumber)?.number==1.0 || (expression as? PtBool)?.value==true
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
internal fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
return IRCodeChunk(label, null).also {
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
@@ -1935,9 +1961,7 @@ class IRCodeGen(
}
}
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
internal fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
val chunk = IRCodeChunk(null, null)
when(registerOrFlag.registerOrPair) {
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
@@ -1954,10 +1978,83 @@ class IRCodeGen(
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
return chunk
}
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, 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)
@@ -52,6 +53,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
|| simplifyConstantReturns(chunk1, indexedInstructions)
} while (changed)
}
}
@@ -84,7 +86,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 +113,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 +358,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
@@ -462,4 +456,23 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
return changed
}
private fun simplifyConstantReturns(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// use a RETURNI when a RETURNR is just returning a constant that was loaded into a register just before
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode==Opcode.RETURNR) {
if(idx>0) {
val insBefore = chunk.instructions[idx-1]
if(insBefore.opcode == Opcode.LOAD && insBefore.immediate!=null) {
val constvalue = insBefore.immediate!!
chunk.instructions[idx] = IRInstruction(Opcode.RETURNI, ins.type, immediate = constvalue)
chunk.instructions.removeAt(idx-1)
changed = true
}
}
}
}
return changed
}
}

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: Iterable<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
}
*/
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?

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

@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -8,7 +6,7 @@ dependencies {
implementation(project(":codeCore"))
implementation(project(":compilerAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}

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,12 +391,11 @@ 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)
val loopvarSimpleDt = loopvar.datatype.base
when(loopvarSimpleDt) {
require(loopvar.datatype.isBasic)
when(val loopvarSimpleDt = loopvar.datatype.base) {
BaseDataType.UBYTE -> {
if(rangeFrom.type != BaseDataType.UBYTE) {
// attempt to translate the iterable into ubyte values

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)
)
}
@@ -242,14 +242,16 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
if (rightVal?.number == 1.0) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if (rightDt != leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
@@ -262,14 +264,16 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
if (rightVal?.number == 1.0) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
else if (rightVal?.number == 0.0) {
if(rightDt!=leftDt) {
val right = NumericLiteral(leftDt.base, rightVal.number, rightVal.position)
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
@@ -423,6 +427,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 +526,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 +543,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 +582,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 +738,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 +866,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 +914,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 +930,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

@@ -19,7 +19,7 @@ class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
if("ignore_unused" !in parent.definingBlock.options())
errors.info("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@@ -29,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))
@@ -342,6 +348,39 @@ class StatementOptimizer(private val program: Program,
}
}
// pointer arithmetic for 6502 target
if (options.compTarget.cpu != CpuType.VIRTUAL) {
if(bexpr.operator=="+" && !bexpr.right.isSimple && !assignment.isAugmentable) {
if(targetIDt.isUnsignedWord || targetIDt.getOrUndef().isPointerToByte) {
val leftDt = bexpr.left.inferType(program).getOrUndef()
val rightDt = bexpr.right.inferType(program).getOrUndef()
fun setSizedValue(a: Assignment, value: Expression, size: Int) {
val sized = BinaryExpression(value, "*", NumericLiteral.optimalInteger(size, value.position), value.position)
a.value = sized
sized.linkParents(a)
}
if (leftDt.isPointer && !leftDt.isPointerToByte) {
// uword x = pointer + value --> x=value * sizeof , x += pointer
val size = leftDt.size(options.compTarget)
setSizedValue(assignment, bexpr.right, size)
val pointerAdd = BinaryExpression(assignment.target.toExpression(), bexpr.operator, bexpr.left, bexpr.position)
val a2 = Assignment(assignment.target.copy(), pointerAdd, assignment.origin, assignment.position)
return listOf(IAstModification.InsertAfter(assignment, a2, parent as IStatementContainer))
} else if (rightDt.isPointer && !rightDt.isPointerToByte) {
// uword x = value + pointer --> x=value * sizeof, x += pointer
val size = rightDt.size(options.compTarget)
setSizedValue(assignment, bexpr.left, size)
assignment.linkParents(parent)
val pointerAdd = BinaryExpression(assignment.target.toExpression(), bexpr.operator, bexpr.right, bexpr.position)
val a2 = Assignment(assignment.target.copy(), pointerAdd, assignment.origin, assignment.position)
return listOf(IAstModification.InsertAfter(assignment, a2, parent as IStatementContainer))
}
}
}
}
}
// word = lsb(word)
@@ -466,7 +505,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
@@ -501,6 +540,27 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
} else {
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
}
}
}
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
}
return noModifications
}

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,18 +16,15 @@ dependencies {
implementation(project(":codeGenExperimental"))
implementation(project(":virtualmachine"))
// implementation(project(":beanshell"))
implementation("org.antlr:antlr4-runtime:4.13.2")
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
testImplementation(project(":codeCore"))
testImplementation(project(":intermediate"))
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
configurations.all {
@@ -83,6 +78,8 @@ gversion {
classPackage = "prog8.buildversion"
className = "Version"
language = "kotlin"
debug = false
annotate = true
}
tasks.build {

View File

@@ -13,9 +13,7 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="compilerAst" />
@@ -25,6 +23,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>

View File

@@ -317,10 +317,25 @@ c128 {
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
extsub $FF6E = JSRFAR()
extsub $FF68 = SETBNK(ubyte databank @A, ubyte filenamebank @X)
extsub $FF47 = SPIN_SPOUT() clobbers(A) ; set up serial bus for fast communications mode
extsub $FF4A = CLOSE_ALL(ubyte device @X) clobbers(X) ; close all channels to specific device
extsub $FF4D = C64_MODE() ; restart machine in C64 mode (does not return)
extsub $FF50 = DMA_CALL(ubyte bank @X, ubyte command @Y) clobbers(A,X) ; send a command to a DMA device
extsub $FF53 = BOOT_CALL(ubyte device @X, ubyte drive @A) clobbers(A,X,Y) ; try to autoboot the given disk
extsub $FF56 = PHOENIX() clobbers(A,X,Y) ; search for and autostart ROMs, cartridges, then default disk
extsub $FF59 = LKUPLA(ubyte lfn @A) -> bool @Pc, ubyte @X ; look up logical file number to see if it's open; returns device
extsub $FF5C = LKUPSA(ubyte sa @Y) -> bool @Pc, ubyte @A, ubyte @X ; look up secondary address to see if it's in use; returns lfn and device
extsub $FF5F = SWAPPER() clobbers(A,X,Y) ; swap active screen (between 40- and 80-column)
extsub $FF62 = DLCHR() clobbers(A,X,Y) ; copy character ROM into VDC video RAM
extsub $FF65 = PFKEY(ubyte zpaddr @A, ubyte key @X, ubyte length @Y) ; redefine programmable function key (string descriptor in zp, addr in A)
extsub $FF68 = SETBNK(ubyte data_bank @A, ubyte filename_bank @X) ; set memory bank for load/save
extsub $FF6B = GETCFG(ubyte bank @X) -> ubyte @A ; translate bank number to MMU configuration register value
extsub $FF6E = JSRFAR() clobbers(A,X) ; call routine in another bank (parameters set in zero page addresses 2-8)
extsub $FF71 = JMPFAR() clobbers(A,X) ; jump without return to another bank (parameters set as for JSRFAR)
extsub $FF74 = INDFET(ubyte zpaddr @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> ubyte @A ; fetch byte from another bank (address in zp, ptr in A)
extsub $FF77 = INDSTA(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) ; store byte to another bank (address in zp, ptr in $02b9)
extsub $FF7A = INDCMP(ubyte value @A, ubyte bank @X, ubyte offset @Y) clobbers(X) -> bool @Pz, bool @Pc, bool @Pv; compare byte in another bank (address in zp, ptr in $02c8)
extsub $FF7D = PRIMM() ; print immediate string
; ---- C128 specific system utility routines: ----
@@ -419,12 +434,14 @@ sys {
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -663,7 +680,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -946,6 +963,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -984,6 +1013,7 @@ cx16 {
&uword r14 = $1bfc
&uword r15 = $1bfe
; signed word versions
&word r0s = $1be0
&word r1s = $1be2
&word r2s = $1be4
@@ -1001,6 +1031,7 @@ cx16 {
&word r14s = $1bfc
&word r15s = $1bfe
; ubyte versions (low and high bytes)
&ubyte r0L = $1be0
&ubyte r1L = $1be2
&ubyte r2L = $1be4
@@ -1035,6 +1066,7 @@ cx16 {
&ubyte r14H = $1bfd
&ubyte r15H = $1bff
; signed byte versions (low and high bytes)
&byte r0sL = $1be0
&byte r1sL = $1be2
&byte r2sL = $1be4
@@ -1069,6 +1101,42 @@ cx16 {
&byte r14sH = $1bfd
&byte r15sH = $1bff
; boolean versions (low and high bytes)
&bool r0bL = $1be0
&bool r1bL = $1be2
&bool r2bL = $1be4
&bool r3bL = $1be6
&bool r4bL = $1be8
&bool r5bL = $1bea
&bool r6bL = $1bec
&bool r7bL = $1bee
&bool r8bL = $1bf0
&bool r9bL = $1bf2
&bool r10bL = $1bf4
&bool r11bL = $1bf6
&bool r12bL = $1bf8
&bool r13bL = $1bfa
&bool r14bL = $1bfc
&bool r15bL = $1bfe
&bool r0bH = $1be1
&bool r1bH = $1be3
&bool r2bH = $1be5
&bool r3bH = $1be7
&bool r4bH = $1be9
&bool r5bH = $1beb
&bool r6bH = $1bed
&bool r7bH = $1bef
&bool r8bH = $1bf1
&bool r9bH = $1bf3
&bool r10bH = $1bf5
&bool r11bH = $1bf7
&bool r12bH = $1bf9
&bool r13bH = $1bfb
&bool r14bH = $1bfd
&bool r15bH = $1bff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -16,26 +16,6 @@ const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
chrout(7)
}

View File

@@ -149,6 +149,29 @@ copy_float .proc
rts
.pend
copy_float2 .proc
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W2,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
; clobbers X

View File

@@ -376,7 +376,7 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 320 word x-lookup table, but hey, that's a lot of memory.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; see https://codebase64.net/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.

View File

@@ -57,7 +57,7 @@ cbm {
; ---- CBM ROM kernal routines (C64 addresses) ----
extsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
extsub $AB1E = STROUT(str strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
extsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
extsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
extsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@@ -439,12 +439,14 @@ sys {
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -685,7 +687,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -968,6 +970,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -1006,6 +1020,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@@ -1023,6 +1038,7 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
&ubyte r2L = $cfe4
@@ -1057,6 +1073,7 @@ cx16 {
&ubyte r14H = $cffd
&ubyte r15H = $cfff
; signed byte versions (low and high bytes)
&byte r0sL = $cfe0
&byte r1sL = $cfe2
&byte r2sL = $cfe4
@@ -1091,6 +1108,42 @@ cx16 {
&byte r14sH = $cffd
&byte r15sH = $cfff
; boolean versions
&bool r0bL = $cfe0
&bool r1bL = $cfe2
&bool r2bL = $cfe4
&bool r3bL = $cfe6
&bool r4bL = $cfe8
&bool r5bL = $cfea
&bool r6bL = $cfec
&bool r7bL = $cfee
&bool r8bL = $cff0
&bool r9bL = $cff2
&bool r10bL = $cff4
&bool r11bL = $cff6
&bool r12bL = $cff8
&bool r13bL = $cffa
&bool r14bL = $cffc
&bool r15bL = $cffe
&bool r0bH = $cfe1
&bool r1bH = $cfe3
&bool r2bH = $cfe5
&bool r3bH = $cfe7
&bool r4bH = $cfe9
&bool r5bH = $cfeb
&bool r6bH = $cfed
&bool r7bH = $cfef
&bool r8bH = $cff1
&bool r9bH = $cff3
&bool r10bH = $cff5
&bool r11bH = $cff7
&bool r12bH = $cff9
&bool r13bH = $cffb
&bool r14bH = $cffd
&bool r15bH = $cfff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -15,25 +15,6 @@ const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
; beep

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

@@ -19,9 +19,9 @@ bmx {
uword palette_entries ; 1-256
ubyte palette_start
ubyte compression
uword @shared palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
^^ubyte @shared palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
uword error_message ; pointer to error message, or 0 if all ok
^^ubyte error_message ; pointer to error message, or 0 if all ok
ubyte old_drivenumber
sub open(ubyte drivenumber, str filename) -> bool {

View File

@@ -121,7 +121,7 @@ io_error:
goto diskio.directory.internal_dir
}
sub diskname() -> uword {
sub diskname() -> str {
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
@@ -165,7 +165,7 @@ io_error:
; internal variables for the iterative file lister / loader
bool list_skip_disk_name
uword list_pattern
^^ubyte list_pattern
uword list_blocks
bool iteration_in_progress = false
bool write_iteration_in_progress = false
@@ -174,7 +174,7 @@ io_error:
; ----- get a list of files (uses iteration functions internally) -----
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
sub list_filenames(str pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
@@ -209,7 +209,7 @@ io_error:
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
sub lf_start_list(str pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
cbm.SETNAM(1, "$")
@@ -239,7 +239,7 @@ io_error:
return false
}
sub lf_start_list_dirs(uword pattern_ptr) -> bool {
sub lf_start_list_dirs(str pattern_ptr) -> bool {
; -- start an iterative directory contents listing with optional pattern matching.
; this version it only returns directory entries!
; note: only a single iteration loop can be active at a time!
@@ -247,7 +247,7 @@ io_error:
goto diskio.lf_start_list.start_list_internal
}
sub lf_start_list_files(uword pattern_ptr) -> bool {
sub lf_start_list_files(str pattern_ptr) -> bool {
; -- start an iterative directory contents listing with optional pattern matching.
; this version only returns actual file entries!
; note: only a single iteration loop can be active at a time!
@@ -267,7 +267,7 @@ io_error:
repeat {
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
^^ubyte nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
ubyte blocks_msb = cbm.CHRIN()
@@ -460,7 +460,7 @@ byte_read_loop: ; fallback if MACPTR isn't supported on the device
return total_read
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
asmsub f_readline(^^ubyte bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character, or 0 byte (likely EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
@@ -590,7 +590,7 @@ return_status:
; ---- other functions ----
sub status() -> uword {
sub status() -> str {
; -- retrieve the disk drive's current status message
; TODO this doesn't seem to work reliably, sometimes READST returns 128 when the drive is just fine
@@ -601,7 +601,7 @@ return_status:
; return device_not_present_error
; }
uword messageptr = &list_filename
^^ubyte messageptr = &list_filename
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
@@ -663,16 +663,16 @@ io_error:
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
sub save(str filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, false)
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
sub save_raw(str filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, true)
}
sub internal_save_routine(uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
sub internal_save_routine(str filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = startaddress + savesize
@@ -719,25 +719,25 @@ io_error:
; the return address will still be one higher. Which means, because the Kernal
; load routine is bank-aware, it will return $a000 and will have switched to the next hiram bank!
; So you'll have to reset the ram bank with cx16.rambank() to switch back to the bank that the data was put in.
sub load(uword filenameptr, uword address_override) -> uword {
sub load(str filenameptr, uword address_override) -> uword {
return internal_load_routine(filenameptr, address_override, false)
}
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details. Including the banking behavior on the X16.
sub load_raw(uword filenameptr, uword startaddress) -> uword {
sub load_raw(str filenameptr, uword startaddress) -> uword {
return internal_load_routine(filenameptr, startaddress, true)
}
; Load a prog8 compiled library binary blob at the given location into memory.
sub loadlib(uword libnameptr, uword libaddress) -> uword {
sub loadlib(str libnameptr, uword libaddress) -> uword {
return internal_load_routine(libnameptr, libaddress, true)
}
sub internal_load_routine(uword filenameptr, uword address_override, bool headerless) -> uword {
sub internal_load_routine(str filenameptr, uword address_override, bool headerless) -> uword {
cbm.SETNAM(strings.length(filenameptr), filenameptr)
ubyte secondary = 1
cx16.r1 = 0
@@ -760,7 +760,7 @@ io_error:
return cx16.r1
}
sub delete(uword filenameptr) {
sub delete(str filenameptr) {
; -- delete a file on the drive
list_filename[0] = 's'
list_filename[1] = ':'
@@ -772,7 +772,7 @@ io_error:
cbm.CLOSE(1)
}
sub rename(uword oldfileptr, uword newfileptr) {
sub rename(str oldfileptr, str newfileptr) {
; -- rename a file on the drive
list_filename[0] = 'r'
list_filename[1] = ':'
@@ -786,7 +786,7 @@ io_error:
cbm.CLOSE(1)
}
sub send_command(uword commandptr) {
sub send_command(str commandptr) {
; -- send a dos command to the drive (don't read any response)
cbm.SETNAM(strings.length(commandptr), commandptr)
cbm.SETLFS(15, drivenumber, 15)
@@ -906,7 +906,7 @@ internal_vload:
send_command(list_filename)
}
sub curdir() -> uword {
sub curdir() -> str {
; return current directory name or 0 if error
; special X16 dos command to only return the current path in the entry list (R42+)
const ubyte MAX_PATH_LEN=80

View File

@@ -47,6 +47,12 @@ emudbg {
}
}
sub console_nl() {
; write a newline to the debug output console.
; because '\n' gets encoded in the x16 value for it (13) which is different than what the shell expects (10).
console_chrout(10)
}
sub console_chrout(ubyte char) {
; note: make sure the character is in Iso encoding.
if is_emulator()

View File

@@ -21,8 +21,8 @@ extsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo'
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
extsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
extsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
extsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
extsub $fe06 = FOUT() clobbers(X) -> str @ AY ; fac1 -> string, address returned in AY
extsub $fe09 = VAL_1(str string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: only implemented in ROM 47+. Safer to use floats.parse() instead.
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)

View File

@@ -825,7 +825,7 @@ set_byte:
cx16.screen_set_charset(charset, 0)
}
sub text(uword @zp xx, uword yy, ubyte color, uword sctextptr) {
sub text(uword @zp xx, uword yy, ubyte color, str sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr

View File

@@ -743,7 +743,7 @@ skip:
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
sub text(uword @zp xx, uword yy, ubyte color, uword textptr) {
sub text(uword @zp xx, uword yy, ubyte color, str textptr) {
; -- Write some text at the given pixel position. The text string must be in an encoding approprite for the charset.
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr

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

@@ -89,18 +89,18 @@ sprites {
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
cx16.VERA_DATA1 = @(xpositions_ptr+num_sprites)
xpositions_ptr ++
}
tovera(xpositions_ptr)
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
cx16.VERA_DATA1 = @(ypositions_ptr+num_sprites)
ypositions_ptr ++
tovera(ypositions_ptr)
sub tovera(uword positions @R0) {
repeat num_sprites {
cx16.VERA_DATA0 = @(cx16.r0)
cx16.VERA_DATA1 = @(cx16.r0+num_sprites)
cx16.r0 ++
}
}
}
@@ -109,20 +109,19 @@ sprites {
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
xpositions_ptr ++
cx16.VERA_DATA1 = @(xpositions_ptr)
xpositions_ptr ++
}
tovera(xpositions_ptr)
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
ypositions_ptr ++
cx16.VERA_DATA1 = @(ypositions_ptr)
ypositions_ptr ++
tovera(ypositions_ptr)
sub tovera(uword positions @R0) {
repeat num_sprites {
cx16.VERA_DATA0 = @(cx16.r0)
cx16.r0 ++
cx16.VERA_DATA1 = @(cx16.r0)
cx16.r0 ++
}
}
}

View File

@@ -177,6 +177,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@@ -194,6 +195,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@@ -228,6 +230,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@@ -262,6 +265,42 @@ cx16 {
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
; VERA registers
const uword VERA_BASE = $9F20
@@ -474,7 +513,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
extsub $C000 = x16edit_default() clobbers(A,X,Y)
@@ -1168,8 +1207,39 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
sub joysticks_detect() -> ubyte {
; returns bits 0-4, set to 1 if that joystick is present.
; bit 0 = keyboard joystick, bit 1 - 4 = joypads 1 to 4
cx16.r0H = 255
for cx16.r0L in 4 downto 0 {
void cx16.joystick_get(cx16.r0L)
%asm {{
cpy #1 ; present?
}}
rol(cx16.r0H)
}
return ~cx16.r0H
}
; Commander X16 IRQ dispatcher routines
sub joysticks_getall(bool also_keyboard_js) -> uword {
; returns combined pressed buttons from all connected joysticks
; note: returns the 'normal' not inverted status bits for the buttons (1 = button pressed.)
cx16.r0H = 1
if also_keyboard_js
cx16.r0H = 0
cx16.r1 = $ffff
for cx16.r0L in cx16.r0H to 4 {
bool notpresent
cx16.r2, notpresent = cx16.joystick_get(cx16.r0L)
if not notpresent {
cx16.r1 &= cx16.r2
}
}
return ~cx16.r1
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
@@ -1216,6 +1286,7 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
@@ -1224,44 +1295,42 @@ _irq_dispatcher
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bit #2
stz _continue_with_system_handler
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
beq +
pha
jsr _line_handler
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
jsr _sprcol_handler
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
jsr _aflow_handler
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
pha
jsr _vsync_handler
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
tsb _continue_with_system_handler
pla
+ lsr a
lsr a
bcc +
pha
jsr _sprcol_handler
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
jsr _aflow_handler
tsb _continue_with_system_handler
+ jsr sys.restore_prog8_internals
lda _continue_with_system_handler
beq _no_sys_handler
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
_no_sys_handler
ply
plx
pla
@@ -1401,7 +1470,7 @@ sub search_x16edit() -> ubyte {
}}
}
sub set_program_args(uword args_ptr, ubyte args_size) {
sub set_program_args(str args_ptr, ubyte args_size) {
; -- Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
@@ -1413,7 +1482,7 @@ sub search_x16edit() -> ubyte {
rambank(sys.pop())
}
asmsub get_program_args(uword buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
asmsub get_program_args(^^ubyte buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
@@ -1485,12 +1554,14 @@ sys {
const ubyte target = 16 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_POINTER = sizeof(&sys.wait)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@@ -1688,7 +1759,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -1997,6 +2068,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@@ -17,26 +17,6 @@ const long VERA_TEXTMATRIX = $1b000
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
chrout(7)
}

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

@@ -6,7 +6,7 @@ cx16logo {
%option ignore_unused
sub logo_at(ubyte column, ubyte row) {
uword strptr
^^ubyte strptr
for strptr in logo_lines {
txt.plot(column, row)
txt.print(strptr)
@@ -15,7 +15,7 @@ cx16logo {
}
sub logo() {
uword strptr
^^ubyte strptr
for strptr in logo_lines {
txt.print(strptr)
txt.nl()

View File

@@ -3,7 +3,7 @@
;
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
; https://codebase64.net/doku.php?id=base:6502_6510_maths
; https://github.com/TobyLobster/multiply_test
; https://github.com/TobyLobster/sqrt_test
@@ -241,6 +241,28 @@ divmod_ub_asm .proc
rts
.pend
remainder_ub_asm .proc
; -- divide A by Y, returns remainder in A (unsigned)
; division by zero will result in just the original number.
; This routine specialcases 0,1,2 and otherwise is just a repeated subtraction.
cpy #0
beq _zero
cpy #1
bne +
lda #0
rts
+ cpy #2
bne +
and #1
rts
+ sty P8ZP_SCRATCH_REG
sec
- sbc P8ZP_SCRATCH_REG
bcs -
adc P8ZP_SCRATCH_REG
_zero rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2
@@ -331,7 +353,7 @@ _divisor .word ?
randword .proc
; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137. NOTE: uses self-modifying code so won't work in ROM (use randword_rom instead)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
@@ -355,7 +377,7 @@ b1=*+1
randword_rom .proc
; -- 16 bit pseudo random number generator into AY. Can run from ROM.
; NOTE: you have to set the initial seed using randseed_rom! (a good default seed = $00c2 $1137)
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
; routine from https://codebase64.net/doku.php?id=6502_6510_maths:x_abc_random_number_generator_8_16_bit
inc _x1
clc
lda _x1

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) {
@@ -388,7 +380,7 @@ _quadrant_region_to_direction:
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
;; https://www.codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{
@@ -555,7 +547,7 @@ log2_tab
}}
}
sub crc16(uword data, uword length) -> uword {
sub crc16(^^ubyte data, uword length) -> uword {
; calculates the CRC16 (XMODEM) checksum of the buffer.
; There are also "streaming" crc16_start/update/end routines below, that allow you to calculate crc16 for data that doesn't fit in a single memory block.
crc16_start()
@@ -610,7 +602,7 @@ log2_tab
return cx16.r15
}
sub crc32(uword data, uword length) {
sub crc32(^^ubyte data, uword length) {
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
@@ -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
@@ -161,7 +163,7 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
@@ -481,6 +483,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@@ -519,6 +533,7 @@ cx16 {
&uword r14 = $7ffc
&uword r15 = $7ffe
; signed word versions
&word r0s = $7fe0
&word r1s = $7fe2
&word r2s = $7fe4
@@ -536,6 +551,7 @@ cx16 {
&word r14s = $7ffc
&word r15s = $7ffe
; ubyte versions (low and high bytes)
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
&ubyte r2L = $7fe4
@@ -570,6 +586,7 @@ cx16 {
&ubyte r14H = $7ffd
&ubyte r15H = $7fff
; signed byte versions (low and high bytes)
&byte r0sL = $7fe0
&byte r1sL = $7fe2
&byte r2sL = $7fe4
@@ -604,6 +621,42 @@ cx16 {
&byte r14sH = $7ffd
&byte r15sH = $7fff
; boolean versions
&bool r0bL = $7fe0
&bool r1bL = $7fe2
&bool r2bL = $7fe4
&bool r3bL = $7fe6
&bool r4bL = $7fe8
&bool r5bL = $7fea
&bool r6bL = $7fec
&bool r7bL = $7fee
&bool r8bL = $7ff0
&bool r9bL = $7ff2
&bool r10bL = $7ff4
&bool r11bL = $7ff6
&bool r12bL = $7ff8
&bool r13bL = $7ffa
&bool r14bL = $7ffc
&bool r15bL = $7ffe
&bool r0bH = $7fe1
&bool r1bH = $7fe3
&bool r2bH = $7fe5
&bool r3bH = $7fe7
&bool r4bH = $7fe9
&bool r5bH = $7feb
&bool r6bH = $7fed
&bool r7bH = $7fef
&bool r8bH = $7ff1
&bool r9bH = $7ff3
&bool r10bH = $7ff5
&bool r11bH = $7ff7
&bool r12bH = $7ff9
&bool r13bH = $7ffb
&bool r14bH = $7ffd
&bool r15bH = $7fff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@@ -15,26 +15,6 @@ const ubyte DEFAULT_HEIGHT = 25
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
sub nl() {
chrout('\n')
}
sub spc() {
chrout(' ')
}
sub bell() {
chrout(7)
}
@@ -225,20 +205,16 @@ sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor_ignored) {
asmsub plot (ubyte col @ Y, ubyte row @ X) {
%asm {{
jsr home
cpy #0
beq +
- lda #29
jsr chrout
dey
bne -
+ cpx #0
beq +
- lda #17
jsr chrout
dex
bne -
+ rts
stx $d8
txa
asl a
tax
lda setchr._screenrows,x
sta $c4
lda setchr._screenrows+1,x
sta $c5
sty $c6
rts
}}
}

View File

@@ -401,8 +401,17 @@ _loop_hi ldy _index_first
.pend
func_peek .proc
; -- read the byte value on the address in AY, into A
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
rts
.pend
func_peekw .proc
; -- read the word value on the address in AY
; -- read the word value on the address in AY, into AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0

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