Compare commits

...

529 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
Irmen de Jong
84026b105f smaller code for goto nosplitptrarray[index] 2025-04-22 20:01:16 +02:00
Irmen de Jong
a4d0589f10 fix errors like parse error still printed in color when -plaintext is set 2025-04-22 19:26:11 +02:00
Irmen de Jong
e375f6afce fix diskio.f_read() returning 1 less than the actual size read.
fix diskio.f_read_all() more robust error status end of loop checking.
2025-04-21 05:02:16 +02:00
Irmen de Jong
5a7bc04816 update docs about library jump table 2025-04-19 13:49:03 +02:00
Irmen de Jong
bd1894580e allow floating point value as part of a multi-value return 2025-04-18 22:45:05 +02:00
Irmen de Jong
9e694c0337 doc 2025-04-18 21:11:27 +02:00
Irmen de Jong
c82586db28 print copyright message better 2025-04-17 23:09:28 +02:00
Irmen de Jong
dd2d466350 code cleanups 2025-04-17 22:58:02 +02:00
Irmen de Jong
830da8de0a memorymapped vars in ZP are now treated as ZP-variables by prog8 itself too 2025-04-17 22:19:01 +02:00
Irmen de Jong
4e5ee333c8 preparing release 11.3 2025-04-17 21:16:52 +02:00
Irmen de Jong
9df899eb63 document romable option and that strings+initialized arrays become read-only 2025-04-17 21:05:46 +02:00
Irmen de Jong
ca7491a702 cx16: sys.enable_irq_handlers() and associated functions are now romable 2025-04-17 00:25:25 +02:00
Irmen de Jong
1a07129865 c64: graphics.horizontal_line() is romable 2025-04-17 00:02:17 +02:00
Irmen de Jong
4fbd67ff99 txt.setcc() is romable 2025-04-16 23:44:50 +02:00
Irmen de Jong
5bc6c50f42 txt.setclr() and getclr() are romable 2025-04-16 23:38:16 +02:00
Irmen de Jong
063de3801d txt.getchr() is romable 2025-04-16 23:32:18 +02:00
Irmen de Jong
ae65266a4a txt.setchr() is romable 2025-04-16 23:27:23 +02:00
Irmen de Jong
8ed2401e0b cx16: txt.scroll_left(), right, up and down are now romable 2025-04-16 23:11:27 +02:00
Irmen de Jong
d2e8ee8269 cx16: txt.fill_screen, txt.clear_screenchars, clear_screencolors are now romable 2025-04-16 22:59:36 +02:00
Irmen de Jong
1f996e3b8b sorting.gnomesort_ub(), compression.decode_rle() and decode_rle_srcfunc() are now romable 2025-04-16 22:47:34 +02:00
Irmen de Jong
7108b74105 string.rfind() is now romable and now works correctly on empty strings.
added some comments to strings.pattern_match
2025-04-16 21:55:55 +02:00
Irmen de Jong
801fe1b604 c64: callfar() is now romable 2025-04-16 21:13:59 +02:00
Irmen de Jong
fb44c87597 make diskio.f_read() ROM-compatible 2025-04-15 00:23:23 +02:00
Irmen de Jong
6b9cdbd482 remove unused arraycopy routines 2025-04-14 22:10:54 +02:00
Irmen de Jong
0ab98033b5 add rom-compatible random number routines 2025-04-14 22:04:11 +02:00
Irmen de Jong
14a2b96609 scanned libraries for self-modifying code/inline vars (romable problem) 2025-04-14 21:15:32 +02:00
Irmen de Jong
f829b689db update zsmkit example to zsmkit release 2.6 2025-04-14 19:28:12 +02:00
Irmen de Jong
dfda8b7ed5 remove problematic rewriting of X=value-X 2025-04-14 02:35:37 +02:00
Irmen de Jong
4388466451 romable comments 2025-04-10 22:21:27 +02:00
Irmen de Jong
5c2f509a52 also hide emulator process output when using -quiet 2025-04-10 21:26:48 +02:00
Irmen de Jong
59582f5210 added -quiet flag to suppres all compiler and assembler messages 2025-04-10 21:16:26 +02:00
Irmen de Jong
e2a8bdbdfb romable comments 2025-04-09 22:35:23 +02:00
Irmen de Jong
0916b943da sys.exit(), exit2(), exit3() now romable 2025-04-09 22:13:52 +02:00
Irmen de Jong
9c7ebc883c fixed: memsetw() invalid asm, c128: sys.restore_irq() 2025-04-09 21:29:48 +02:00
Irmen de Jong
0ee42b9aa0 output_type is now part of custom target config and atari again defaults to .xex 2025-04-09 20:43:44 +02:00
Irmen de Jong
37b3868ca3 symboldump now also includes aliased symbols (like palette.set_default) 2025-04-08 21:40:28 +02:00
Irmen de Jong
a6835ce3f0 fix signed word value range check error, fix rol2() on array element 2025-04-08 21:05:50 +02:00
Irmen de Jong
69c96ad99b cx16: added cx16.push_rambank/rombank and cx16.pop_rambank/rombank for easy temporary bank switching 2025-04-06 15:33:35 +02:00
Irmen de Jong
b72877d59d cx16: added routines to get and set the default palette (new rom 49+ extapi) 2025-04-06 15:14:04 +02:00
Irmen de Jong
05eb15d4f7 add cx16.memory_decompress_from_func() 2025-04-06 13:48:08 +02:00
Irmen de Jong
f1fec37c79 rename examples/vm to examples/virtual 2025-04-04 20:02:45 +02:00
Irmen de Jong
73f6880ff8 fix irq related crash by no longer zeroing out stored vector 2025-04-02 22:22:21 +02:00
Irmen de Jong
8a53742f31 fix notreached error 2025-04-01 23:12:08 +02:00
adiee5
9be40e85ff Improve romability of low-level libs (#160)
* Improve romability of low-level libs

* Improve ROMability of targeted syslibs

Previous commit just did that for multitarget libs, now it's also syslibs of built-in targets that get this treatment too.
2025-04-01 23:00:14 +02:00
Irmen de Jong
61079c1eb7 errormessage for assignment to str/arrays in ROM 2025-03-31 23:57:04 +02:00
Irmen de Jong
1075ee8fc3 errormessage for non romable extsub bank declaration 2025-03-31 23:05:58 +02:00
Irmen de Jong
a28b265197 fix diskio merge issue on c64 and c128. Fixes #167 2025-03-31 22:06:49 +02:00
Irmen de Jong
20e534c468 fix memtop limit when using -varshigh option 2025-03-27 23:31:05 +01:00
Irmen de Jong
da7aa5dc49 forloop temp index variables no longer inline when romable 2025-03-26 22:38:50 +01:00
Irmen de Jong
8f2a43ca0a temp vars for for loops indices in romable mode, are no longer allocated from the precious Zeropage 2025-03-26 22:12:22 +01:00
Irmen de Jong
d0909d7810 added diskio.loadlib() convenience function to load library blobs 2025-03-24 22:35:34 +01:00
Irmen de Jong
1641999d20 upgrade to kotlin 2.1.20 2025-03-22 23:06:44 +01:00
Irmen de Jong
e16452037c no more self modifying code for loops over non-const word range 2025-03-22 22:51:30 +01:00
Irmen de Jong
344d79684a get rid of more modifying code in forloops (byte ranges with step 1/-1)
fix invalid stack corrupting loops
2025-03-22 20:45:28 +01:00
Irmen de Jong
573a1d9b7b refactor for loop over byte ranges 2025-03-22 16:07:46 +01:00
Irmen de Jong
25ab57580c for loop over string no longer requires self-modifying code 2025-03-22 13:38:49 +01:00
Irmen de Jong
a332e0e3d1 remove self-modifying code from @(ptr)++ and @(ptr)--
romable errors for string and array variables turned into read-only warnings

remove self-modifying code from @(ptr)++ and @(ptr)--
2025-03-21 23:54:19 +01:00
Irmen de Jong
376f1cb139 remove self-modifying code from rol(ptr) and ror(ptr) 2025-03-21 23:16:40 +01:00
Irmen de Jong
90f80558d7 add more 'modified' comments to self-modifying code lines 2025-03-21 17:59:49 +01:00
Irmen de Jong
e281994898 intellij syntax def file works for all jetbrains ides 2025-03-21 01:32:24 +01:00
markjreed
29fac122e1 fix: ignore EOF when looking for error status after reading first byte of newly-opened file (#166)
* fix: ignore EOF when looking for error status after reading first byte of newly-opened file

* fix: replace hard-coded $40 and $BF with STATUS_EOF and ~STATUS_EOF
2025-03-19 23:00:55 +01:00
Irmen de Jong
1dc412eb90 simplify 2025-03-18 23:50:51 +01:00
Irmen de Jong
3770a4fe0c easier datatype notation by just using the type objects directly 2025-03-18 23:33:04 +01:00
Irmen de Jong
79cda544c8 allow integer range as when choice value 2025-03-17 22:26:27 +01:00
Irmen de Jong
f04b97d890 support symlinks in paths (triggered by symlinked tmp on mac os) 2025-03-15 14:33:33 +01:00
Irmen de Jong
3e9b4ccc45 getting rid of needless absolute pathings 2025-03-14 20:39:18 +01:00
Irmen de Jong
2c3d838dd8 get rid of deprecated usage of thread id to create temporary test output file names 2025-03-14 01:23:01 +01:00
Irmen de Jong
7668a3c660 use kotest tempdir instead of hardcoded output directory 2025-03-14 01:18:33 +01:00
Irmen de Jong
5dd45b714a psg module: the envelope handler no longer writes to vera PSG voices that haven't been enabled 2025-03-12 23:07:13 +01:00
Irmen de Jong
8b08895d0f also add strings.ncompare() to virtual lib 2025-03-09 14:33:31 +01:00
Irmen de Jong
8f8d99e3ed updated symboldumps 2025-03-07 23:58:06 +01:00
Irmen de Jong
23474360ec fix ncompare calls 2025-03-07 23:56:13 +01:00
gillham
81c255c450 Add strings.ncompare (and backing strncmp_mem) to compare up to n characters of a string. (#164)
* Add strings.ncompare (and backing strncmp_mem) to compare up to n characters of a string.

* Document strings.ncompare.
2025-03-07 23:53:56 +01:00
Irmen de Jong
ef23d52ed7 better error if module name clash occurs caused by case-insensitive names 2025-03-07 21:35:35 +01:00
Irmen de Jong
220ab773aa fix asmgen error when assigning address of split word array without explicit adressof syntax 2025-03-06 23:20:42 +01:00
Irmen de Jong
e3e5bff7bb add missing sprites benchmark module 2025-03-06 19:39:55 +01:00
Irmen de Jong
7b9a841b2a fix C64 graphics.plot() it was broken since 10.5 2025-03-06 00:42:17 +01:00
Irmen de Jong
40423911ef added footgun warning when calling labels as subroutine 2025-03-04 22:14:21 +01:00
Irmen de Jong
582a70b046 fix calling label as subroutine (JSR label) 2025-03-04 21:53:53 +01:00
Irmen de Jong
5b63590ebf fix symbol prefixing bug triggered by certain usage of %option no_symbol_prefixing 2025-03-03 22:26:19 +01:00
Irmen de Jong
2b6510dc19 improve error message for non-numeric when choice values 2025-03-03 19:48:45 +01:00
Irmen de Jong
9d49589d73 fix codegen for @(ptr-offset)=value writing bogus values 2025-03-02 23:40:14 +01:00
Irmen de Jong
125b66c929 fix crash in asmgen for boolean comparison with false, when not using optimizations 2025-03-01 23:57:55 +01:00
Irmen de Jong
5255f1c052 print offending source position in more of the TODO errors to help diagnosing 2025-03-01 23:16:34 +01:00
Irmen de Jong
a6ba05d60c compile time evaluation of constant rangecheck 2025-03-01 13:45:11 +01:00
Irmen de Jong
41e963b04b memory() name argument should be string literal, nice error message 2025-03-01 12:38:42 +01:00
Irmen de Jong
6ff75bef29 fix verafx register address typo VERA_FX_POLY_FILL_H 2025-02-27 21:59:04 +01:00
Irmen de Jong
72c16d0d32 avoid doing useless jsr for reboot/poweroff calls 2025-02-26 20:42:38 +01:00
Irmen de Jong
94653e5c8c possible workaround for SMC issue that could make sys.reset_system() and sys.poweroff_system() not work properly 2025-02-24 23:07:51 +01:00
Irmen de Jong
3e2b2a698d Separate simple Ast and Symboltable from codeCore into new simpleAst module. VirtualMachine and Intermediate do not need them, just codeCore. 2025-02-24 22:06:52 +01:00
Irmen de Jong
ae04f5aee8 %option romable now disables floating point support
because if it's your code that is running in the rom bank you can't have the floating point routines available there
2025-02-23 10:21:55 +01:00
Irmen de Jong
5c56267662 Also romable warning for inline variables. Added TODO: Romable in library files where applicable 2025-02-21 21:58:31 +01:00
Irmen de Jong
e55ce5504e added %option romable to enable romable mode, but only generate a bunch of warnings for problematic codegeneration atm 2025-02-20 23:40:44 +01:00
Irmen de Jong
fb1e89d9ef update docs about call convention for multi-value results (first is in A or AY, then R15...R0)
added sprites+coroutines+defer part to benchmark program
2025-02-19 22:10:12 +01:00
Irmen de Jong
bc550a4549 fix optimized multi-value call result assignment 2025-02-19 20:19:20 +01:00
Irmen de Jong
ebdea9cf76 optimized call convention for multi-value return and assign on regular asmsubs (6502) 2025-02-19 20:19:20 +01:00
Irmen de Jong
09ec508f82 avoid unnecessary word extension when assigning a register byte to a byte target 2025-02-19 20:19:20 +01:00
Irmen de Jong
d06e9ea7f6 allow comparisons against constant values with different type 2025-02-19 20:19:20 +01:00
Mia McMahill
a36bdc54fd Add Notepad++ syntax highlighting file for dark mode (#161) 2025-02-19 18:33:31 +01:00
Irmen de Jong
0814ea9711 symboldumps 2025-02-13 13:10:07 +01:00
Irmen de Jong
daefe839d8 version 11.1 2025-02-13 12:31:12 +01:00
Irmen de Jong
e6088dd315 optimize byte comparisons against small word constants 2025-02-13 12:02:11 +01:00
Irmen de Jong
fc03d6f332 changed -sourcelines option to -nosourcelines (default is now to include them) 2025-02-12 21:27:46 +01:00
Irmen de Jong
2aeb7a838e finalize extracting neo and atari compiler targets into configuration files instead 2025-02-12 14:01:04 +01:00
Irmen de Jong
99ff5dd078 extracting neo and atari compiler targets into configuration files instead 2025-02-12 13:58:34 +01:00
Irmen de Jong
49982b49b6 extracting neo and atari compiler targets into configuration files instead 2025-02-12 13:58:34 +01:00
Irmen de Jong
fd39c22616 Update FUNDING.yml
added paypal sponsor link
2025-02-12 13:48:49 +01:00
Irmen de Jong
9e79722a7f fix rangeexpression value type casting 2025-02-11 22:23:07 +01:00
Irmen de Jong
17334a1c58 fix 6502 codegen error for mixed case register params and normal params 2025-02-10 22:06:11 +01:00
Irmen de Jong
c7f0ff11ac fix crash when initializing string variable with a non-string value 2025-02-10 02:54:53 +01:00
Irmen de Jong
cd2cc89e6a fix type errors in Range containment check 2025-02-10 02:27:34 +01:00
Irmen de Jong
069143092d fix Golden ram issues 2025-02-09 15:57:14 +01:00
Irmen de Jong
efd41260f2 added %jmptable 2025-02-09 15:02:59 +01:00
Irmen de Jong
8d2410622c make 64tass less strict about implied register addressing modes on instructions like "asl" 2025-02-05 22:33:50 +01:00
Irmen de Jong
60554389b3 for fileselector library save/restore the prog8 temporary ZP locations 2025-02-04 00:17:55 +01:00
Irmen de Jong
a940dc7d43 improve dumpvars output for zeropage variables 2025-02-03 22:36:58 +01:00
Irmen de Jong
06ca68a625 add drivenumber to fileselector config 2025-02-02 23:09:27 +01:00
Irmen de Jong
5b58e5b158 fix unit tests 2025-02-02 21:52:33 +01:00
Irmen de Jong
74dd8fe80b %output library header generation depends on compiler target
fileselector example tweaks
2025-02-02 20:51:45 +01:00
Irmen de Jong
75ddcda5f3 simplify DirectiveArg 2025-02-02 04:35:20 +01:00
Irmen de Jong
216825b98a cx16: made fileselector example into a loadable library 2025-02-02 04:13:03 +01:00
Irmen de Jong
a96defab86 diskio: added several diskio routines to list only the files or dir entries on the disk
uses CBM DOS filtering $:*=c and $:*=p
2025-02-02 02:44:23 +01:00
Irmen de Jong
0864b0a1b7 cx16: added several diskio routines to list only the files or dir entries on the disk
uses CBM DOS filtering $:*=d and $:*=p
2025-02-01 22:41:34 +01:00
Irmen de Jong
8b158d9240 fix 64tass ascii encoding option for atari and neo6502 2025-02-01 21:47:01 +01:00
Irmen de Jong
f335251c2b added ability to specify additional assembler options in custom target configurations 2025-02-01 16:09:43 +01:00
Tymoteusz Moryto
67bc0b6931 Added txt.cls() to prog8_builtins.vim (#159) 2025-02-01 14:21:40 +01:00
gillham
e646dd1ed1 Add an encoding for the C64 OS custom character set. Use c64os: (#158) 2025-01-31 23:41:55 +01:00
Irmen de Jong
2b7947f9b0 fix address check issue when using custom launcher 2025-01-31 23:34:12 +01:00
Irmen de Jong
ec0cfb4b3f doc fix 2025-01-30 23:55:14 +01:00
Irmen de Jong
9cdf53019c some tweaks to the configurable targets 2025-01-30 23:52:44 +01:00
Irmen de Jong
1a04a3eb3a added ability to configure custom ASM launcher code in target configuration file 2025-01-30 22:10:42 +01:00
Irmen de Jong
105d3995e0 some docs about the configurable targets, update kotlin to 2.1.10 2025-01-29 21:14:40 +01:00
Irmen de Jong
8ce3204f93 program start label is back for library outputs 2025-01-28 19:28:34 +01:00
Irmen de Jong
d0f15f1285 missing pic 2025-01-27 23:40:32 +01:00
Irmen de Jong
66d6f67120 usage 2025-01-27 23:36:39 +01:00
Irmen de Jong
a106c88054 unit test for %output library, and docs. 2025-01-27 23:26:21 +01:00
Irmen de Jong
ee784e1ccc fix indication for when imported modules are library modules or not.
This fixes a bug where syslib and such gets optimized away when it is loaded from an alternative library location using the configurable target library path property setting.
2025-01-26 21:19:29 +01:00
Irmen de Jong
bb75be0b44 library now includes the 2 byte PRG header
fixed some assorted things
2025-01-26 19:48:44 +01:00
Irmen de Jong
2478aea316 add %output library 2025-01-24 23:25:57 +01:00
Irmen de Jong
1e17df5296 optimize word+byte*2 expression to word+byte+byte (more efficient in 6502 codegen) 2025-01-24 21:30:02 +01:00
Irmen de Jong
8583a96519 optimized gnomesorts 2025-01-24 00:40:11 +01:00
Irmen de Jong
d0c184c7de remove needless 0 initializations of multi decl's
fix outdated text
2025-01-23 22:42:58 +01:00
Irmen de Jong
0191acb2b3 fix IR codegen for the RETURN 4,5,6,7
added cx16.EXTAPI_memory_decompress_from_func for cx16
2025-01-22 23:31:51 +01:00
Irmen de Jong
277a1a32b2 fix crash when declaring a string array with wrong variable type str vs str[] 2025-01-21 03:12:12 +01:00
Irmen de Jong
7a13f57ab0 enforce variable init values are only strings or arrays 2025-01-20 01:25:17 +01:00
Irmen de Jong
0c882836d9 support multi-value variable initialization: ubyte a,b,c = multi() 2025-01-18 22:08:31 +01:00
Irmen de Jong
228be5cd04 callgraph no longer forgets some identifier occurrences 2025-01-18 21:18:08 +01:00
Irmen de Jong
08cd2fd6e8 fix check for assigning to a constant, for multi-value assigns. 2025-01-18 20:38:02 +01:00
Irmen de Jong
bc7b086f0f fix a configurable compilation target, add working example 2025-01-17 22:58:51 +01:00
Irmen de Jong
e8f3af6981 adding a configurable compilation target 2025-01-17 00:56:44 +01:00
Irmen de Jong
f9c7c7dab7 fix subroutine calling convention for @Rx parameters: don't pass them via cpu registers 2025-01-14 23:02:17 +01:00
Irmen de Jong
09a17743ad merge IMachineDefinition into ICompilationTarget 2025-01-13 21:45:38 +01:00
Irmen de Jong
4f096a7511 added sprites.getxy()
fix compiler crash on return xxx,yyy  when symbol is not defined
2025-01-10 22:26:38 +01:00
Irmen de Jong
2ab2130000 renamed "intermediate AST" to "simplified AST" 2025-01-10 20:33:44 +01:00
Irmen de Jong
66558f7638 IR support for multi-value returns in normal subroutines, documentation. 2025-01-09 22:39:08 +01:00
Irmen de Jong
a6f9ed07e7 6502 codegen for multi-assigns 2025-01-09 00:38:47 +01:00
Irmen de Jong
7268a8736f working on 6502 codegen for multi-assigns 2025-01-07 22:13:13 +01:00
Irmen de Jong
8f6b5676d7 working on codegen for multi-value returns 2025-01-07 20:35:49 +01:00
Irmen de Jong
ca9422bbe9 be able to parse multiple return values (sub + return) 2025-01-07 01:51:54 +01:00
Irmen de Jong
35d9412559 better error message 2025-01-07 00:53:48 +01:00
Irmen de Jong
f071c07dd9 IR: only put align on lsb array of split word array. tag split word arrays with split. (but this isn't actually used yet) 2025-01-06 02:50:54 +01:00
Irmen de Jong
e5ff3c1ff3 fix compiler crash when using strings in if-expression. Remove harmless info message. 2025-01-06 01:34:42 +01:00
Irmen de Jong
f0e8ff0326 get rid of some empty lines in the generated asm 2025-01-05 19:34:05 +01:00
Irmen de Jong
3b5cda85ff fix register clobber on @() 2025-01-05 16:34:51 +01:00
Irmen de Jong
420793f9e2 mkword() avoids unneeded push/pop for simple arguments 2025-01-05 13:41:40 +01:00
Irmen de Jong
cf1dbaf0d8 no longer clear msb on asmsub argument byte @R0
avoid unneeded push/pop for 2 byte arguments to a functioncall
2025-01-05 13:06:39 +01:00
Irmen de Jong
d187cef6b7 optimize x=min(x,100) and some other simple min() and max() cases 2025-01-05 11:59:00 +01:00
Irmen de Jong
3b3616afda optimized pointer access @(pointer - constantoffset) 2025-01-05 06:31:34 +01:00
Irmen de Jong
0ffebc25d0 tweak pointer access, fix and optimize reading memory from a label as pointer 2025-01-05 05:44:51 +01:00
Irmen de Jong
478e2b4ebd abs() return type changed to ubyte/uword 2025-01-04 23:16:51 +01:00
Irmen de Jong
a56ae7539a optimized codegen for word*128 (word << 7): no longer do 7 shifts 2025-01-04 23:08:21 +01:00
Irmen de Jong
407773bda2 IR: don't lose inline asm chunks, fix carry behavior for multi-shifts 2025-01-04 22:12:54 +01:00
Irmen de Jong
823eaa8918 when using @requirezp in a subroutine that is unused (and is pruned), don't give an error there when using %zeropage dontuse
give warning when a pointer var is declared as @nozp  (very inefficient)
2025-01-04 16:39:09 +01:00
Irmen de Jong
a2be42c5ca txt.get_cursor() now returns the column and row as 2 values, no longer requires 2 pointer arguments 2025-01-04 04:06:27 +01:00
Irmen de Jong
a76b8d66ff cx16: added syslib.get_charset()
updated fileselector
2025-01-04 01:02:43 +01:00
Irmen de Jong
b7f47d354f IR: implemented << and >> for split word arrays 2025-01-03 22:18:02 +01:00
Irmen de Jong
5d33c93af9 IR: implemented rol(), rol2(), ror(), ror2() for split word arrays 2025-01-03 20:01:50 +01:00
Irmen de Jong
4db6859f3f IR: strict register pool type assignment, 1 unique type per register + verification during IR writing 2025-01-03 04:18:16 +01:00
Irmen de Jong
45fe1bb16e reduce error clutter for undefined symbols 2025-01-03 01:55:44 +01:00
Irmen de Jong
b014facbd3 cx16 fileselector improvements 2025-01-03 01:35:20 +01:00
Irmen de Jong
3b4b37f16b optimize codegen for x += array[index] (and others) 2025-01-02 01:11:25 +01:00
Irmen de Jong
68d5983a14 optimize monogfx.plot() to use a *40 lookup table in lores mode. Speeds up a lot of other routines too (line etc) 2025-01-01 20:25:58 +01:00
Irmen de Jong
f2cfcfdf31 cx16: monogfx can fill with stipple mode again.
cx16: charset switching enabled again on program exit.
cx16: fileselector example: can now also show directories
2025-01-01 18:27:25 +01:00
Irmen de Jong
10b9162dc5 improving fileselector 2024-12-30 21:34:03 +01:00
Irmen de Jong
c84cc8f8c9 word arrays for sorting should be @nosplit 2024-12-30 00:00:31 +01:00
Irmen de Jong
78c71bbf0e adding file selector example 2024-12-30 00:00:31 +01:00
Irmen de Jong
37c2c1bf0b support &, &< and &> on array elements from split word arrays, not just on the array as a whole 2024-12-30 00:00:31 +01:00
Irmen de Jong
c8996418da ir: tweak register pool, prepare to register the types there uniquely 2024-12-30 00:00:31 +01:00
Irmen de Jong
76b29aa629 fixed register reuse and types on syscall interface 2024-12-30 00:00:31 +01:00
Irmen de Jong
ee521793f8 ir: enforce single reg type 2024-12-30 00:00:31 +01:00
Irmen de Jong
f42e12bc13 ir: fix more register typing errors 2024-12-30 00:00:31 +01:00
Irmen de Jong
427451a23f ir: remove remaining SL* opcodes 2024-12-30 00:00:31 +01:00
Irmen de Jong
af7930d494 ir: remove SL* opcodes 2024-12-30 00:00:31 +01:00
Irmen de Jong
e2882d37bf ir: remove a bunch of strange in-place assignment operators, and problematic opcodes 2024-12-30 00:00:31 +01:00
Irmen de Jong
942d3ee640 ir: improve register type detection 2024-12-30 00:00:31 +01:00
Irmen de Jong
7b4a82b91a IR: report register usage and types in code blocks 2024-12-30 00:00:31 +01:00
Irmen de Jong
056c0a24d9 better way of doing BIT instructions 2024-12-30 00:00:31 +01:00
Irmen de Jong
827df04b32 IR: BIT instruction added 2024-12-30 00:00:31 +01:00
Irmen de Jong
e174b31344 IR: fix up missing indirect goto codegen 2024-12-30 00:00:31 +01:00
Irmen de Jong
49959af752 IR: support %align in code chunks, and load/store FAC0/FAC1 2024-12-30 00:00:31 +01:00
385 changed files with 31363 additions and 11908 deletions

3
.github/FUNDING.yml vendored
View File

@@ -2,7 +2,7 @@
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
#open_collective: # Replace with a single Open Collective username
ko_fi: irmen
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
@@ -11,3 +11,4 @@ ko_fi: irmen
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://paypal.me/irmendejong']

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.0" />
<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.0" />
<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.0/kotlin-stdlib-jdk8-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.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.0/kotlin-stdlib-jdk7-2.1.0.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.0/kotlin-stdlib-jdk8-2.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0-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.0/kotlin-stdlib-jdk7-2.1.0-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.0/kotlin-stdlib-jdk8-2.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0-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.0/kotlin-stdlib-jdk7-2.1.0-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

@@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1" />
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.23.1/org.eclipse.lsp4j-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.23.1/org.eclipse.lsp4j.jsonrpc-0.23.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
</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.0" />
<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.0/kotlin-result-jvm-2.0.0.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.0/kotlin-result-jvm-2.0.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/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.0/kotlin-result-jvm-2.0.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/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>

6
.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>
@@ -22,7 +23,10 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<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>

1
.idea/modules.xml generated
View File

@@ -17,6 +17,7 @@
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>

View File

@@ -1,4 +1,7 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/H2H6S0FFF)
PayPal: [![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://paypal.me/irmendejong)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
@@ -14,7 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen).
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
Documentation
@@ -58,18 +61,23 @@ 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
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- programs can be configured to execute in ROM
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- high-level program optimizations
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- 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``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- subroutines can return more than one result value
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
@@ -89,8 +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)
- "atari": Atari 8 bit such as 800XL (experimental)
- "neo": Neo6502 (experimental)
- 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

@@ -12,7 +12,7 @@ circles {
sub draw(bool use_kernal, uword max_time) -> uword {
if use_kernal
void cx16.set_screen_mode(128)
cx16.set_screen_mode(128)
else
gfx_lores.graphics_mode()
@@ -33,7 +33,7 @@ circles {
}
if use_kernal
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
else {
gfx_lores.text_mode()
}

View File

@@ -0,0 +1,69 @@
%import sprites
%import coroutines
%import math
animsprites {
uword num_iterations
ubyte[64] sx
ubyte[64] sy
ubyte[64] sc
ubyte[64] dx
ubyte[64] dy
uword maximum_duration
sub benchmark(uword max_duration) -> uword {
maximum_duration = max_duration
math.rndseed(1122,9876)
cx16.set_screen_mode(3)
cx16.mouse_config2(1)
sprites.set_mousepointer_hand()
repeat 64
void coroutines.add(animsprite, 0)
cx16.mouse_config2(0)
cbm.SETTIM(0,0,0)
coroutines.run(supervisor)
sprites.reset(0, 64)
return num_iterations
}
sub supervisor() -> bool {
if cbm.RDTIM16() >= maximum_duration {
coroutines.killall()
return false
}
return true
}
sub animsprite() {
num_iterations++
; set up the sprite
ubyte sprnum = coroutines.current()
cx16.r6L, cx16.r7 = sprites.get_data_ptr(0)
sprites.init(sprnum, cx16.r6L, cx16.r7, sprites.SIZE_16, sprites.SIZE_16, sprites.COLORS_256, 0)
sx[sprnum] = math.rnd()
sy[sprnum] = math.rnd()
sc[sprnum] = math.rnd()
dx[sprnum] = if math.rnd()&1 == 1 1 else 255
dy[sprnum] = if math.rnd()&1 == 1 1 else 255
; move the sprite around
while sc[sprnum]!=0 {
animate(sprnum)
void coroutines.yield()
sprnum = coroutines.current()
}
sub animate(ubyte spr) {
defer sc[spr]--
sprites.pos(spr, sx[spr], sy[spr])
sx[spr] += dx[spr]
sy[spr] += dy[spr]
}
; end the task but replace it with a fresh animated sprite task
void coroutines.add(animsprite, 0)
}
}

View File

@@ -15,6 +15,7 @@
%import b_queens
%import b_textelite
%import b_maze
%import b_sprites
%zeropage basicsafe
%option no_sysinit
@@ -29,7 +30,7 @@ main {
sub start() {
ubyte benchmark_number
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.color2(1, 6)
txt.clear_screen()
@@ -74,10 +75,14 @@ main {
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
announce_benchmark("sprites-coroutines-defer")
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
@@ -99,7 +104,7 @@ main {
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
void cx16.set_screen_mode(3)
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
txt.clear_screen()

View File

@@ -1,7 +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.0"
kotlin("jvm") version "2.2.10"
}
allprojects {
@@ -16,6 +19,13 @@ allprojects {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
jvmTarget = JvmTarget.JVM_11
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
sourceSets.all {
languageSettings {
// enable language features like so:
// enableLanguageFeature(LanguageFeature.WhenGuards.name)
}
}
}

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.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {
@@ -21,4 +19,4 @@ sourceSets {
}
}
// note: there are no unit tests in this module!
// note: there are no unit tests in this module!

View File

@@ -0,0 +1,31 @@
package prog8.code
import java.io.IOException
import java.nio.file.Path
import kotlin.io.path.absolute
// the automatically generated module where all string literals are interned to:
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
/**
* Returns the absolute path of the given path,
* where links are replaced by the actual directories,
* and containing no redundant path elements.
* If the path doesn't refer to an existing directory or file on the file system,
* it is returned unchanged.
*/
fun Path.sanitize(): Path {
return try {
this.toRealPath().normalize()
} catch (_: java.nio.file.NoSuchFileException) {
this.absolute().normalize()
//throw NoSuchFileException(this.toFile(), null, nx.reason).also { it.initCause(nx) }
} catch (iox: IOException) {
throw FileSystemException(this.toFile()).also { it.initCause(iox) }
}
}

View File

@@ -1,193 +0,0 @@
package prog8.code.ast
import prog8.code.core.*
sealed interface IPtSubroutine {
val name: String
val scopedName: String
}
class PtAsmSub(
name: String,
val address: Address?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position), IPtSubroutine {
class Address(val constbank: UByte?, var varbank: PtIdentifier?, val address: UInt)
}
class PtSub(
name: String,
val parameters: List<PtSubroutineParameter>,
val returntype: DataType?,
position: Position
) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer {
init {
// params and return value should not be str
if(parameters.any{ !it.type.isNumericOrBool })
throw AssemblyError("non-numeric/non-bool parameter")
if(returntype!=null && !returntype.isNumericOrBool)
throw AssemblyError("non-numeric/non-bool returntype $returntype")
parameters.forEach { it.parent=this }
}
}
class PtSubroutineParameter(name: String, val type: DataType, val register: RegisterOrPair?, position: Position): PtNamedNode(name, position)
sealed interface IPtAssignment {
val children: MutableList<PtNode>
val target: PtAssignTarget
get() {
if(children.size==2)
return children[0] as PtAssignTarget
else if(children.size<2)
throw AssemblyError("incomplete node")
else
throw AssemblyError("no singular target")
}
val value: PtExpression
get() = children.last() as PtExpression
val multiTarget: Boolean
get() = children.size>2
}
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
class PtAssignTarget(val void: Boolean, position: Position) : PtNode(position) {
val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier
val array: PtArrayIndexer?
get() = children.single() as? PtArrayIndexer
val memory: PtMemoryByte?
get() = children.single() as? PtMemoryByte
val type: DataType
get() {
return when(val tgt = children.single()) {
is PtIdentifier -> tgt.type
is PtArrayIndexer -> tgt.type
is PtMemoryByte -> tgt.type
else -> throw AssemblyError("weird target $tgt")
}
}
infix fun isSameAs(expression: PtExpression): Boolean = !void && expression.isSameAs(this)
}
class PtConditionalBranch(val condition: BranchCondition, position: Position) : PtNode(position) {
val trueScope: PtNodeGroup
get() = children[0] as PtNodeGroup
val falseScope: PtNodeGroup
get() = children[1] as PtNodeGroup
}
class PtForLoop(position: Position) : PtNode(position) {
val variable: PtIdentifier
get() = children[0] as PtIdentifier
val iterable: PtExpression
get() = children[1] as PtExpression
val statements: PtNodeGroup
get() = children[2] as PtNodeGroup
}
class PtIfElse(position: Position) : PtNode(position) {
val condition: PtExpression
get() = children[0] as PtExpression
val ifScope: PtNodeGroup
get() = children[1] as PtNodeGroup
val elseScope: PtNodeGroup
get() = children[2] as PtNodeGroup
fun hasElse(): Boolean = children.size==3 && elseScope.children.isNotEmpty()
}
class PtJump(position: Position) : PtNode(position) {
val target: PtExpression
get() = children.single() as PtExpression
}
class PtRepeatLoop(position: Position) : PtNode(position) {
val count: PtExpression
get() = children[0] as PtExpression
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
}
class PtReturn(position: Position) : PtNode(position) {
val hasValue: Boolean
get() = children.any()
val value: PtExpression?
get() {
return if(children.any())
children.single() as PtExpression
else
null
}
}
sealed interface IPtVariable {
val name: String
val type: DataType
}
class PtVariable(
name: String,
override val type: DataType,
val zeropage: ZeropageWish,
val align: UInt,
val value: PtExpression?,
val arraySize: UInt?,
position: Position
) : PtNamedNode(name, position), IPtVariable {
init {
value?.let {it.parent=this}
}
}
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
init {
require(!type.isString)
}
}
class PtWhen(position: Position) : PtNode(position) {
val value: PtExpression
get() = children[0] as PtExpression
val choices: PtNodeGroup
get() = children[1] as PtNodeGroup
}
class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
val values: PtNodeGroup
get() = children[0] as PtNodeGroup
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
}
class PtDefer(position: Position): PtNode(position), IPtStatementContainer

View File

@@ -1,16 +0,0 @@
package prog8.code.ast
import prog8.code.SymbolTable
import prog8.code.core.*
fun verifyFinalAstBeforeAsmGen(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
/*
walkAst(program) { node, _ ->
if(node is PtVariable) {
if(node.value!=null) {
// require(node.type in ArrayDatatypes || node.type==DataType.STR) { "final check before asmgen: only string and array variables can still have an init value ${node.name} ${node.type} ${node.position}"}
}
}
}
*/
}

View File

@@ -27,8 +27,6 @@ class FParam(val name: String, vararg val possibleDatatypes: BaseDataType)
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
private val IntegerDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
private val ByteDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
private val ArrayDatatypes = arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
class FSignature(val pure: Boolean, // does it have side effects?
@@ -69,7 +67,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
}
CallConvention(listOf(paramConv), returns)
}
actualParamTypes.size==2 && (actualParamTypes[0] in ByteDatatypes && actualParamTypes[1].isWord) -> {
actualParamTypes.size==2 && (actualParamTypes[0].isByte && actualParamTypes[1].isWord) -> {
TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet")
}
actualParamTypes.size==2 && (actualParamTypes[0].isWord && actualParamTypes[1].isByte) -> {
@@ -98,14 +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_ifelse_bittest_set" to FSignature(true, BaseDataType.BOOL, FParam("variable", *ByteDatatypes), FParam("bitnumber", BaseDataType.UBYTE)),
"prog8_ifelse_bittest_notset" to FSignature(true, BaseDataType.BOOL, FParam("variable", *ByteDatatypes), FParam("bitnumber", BaseDataType.UBYTE)),
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD)),
"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)),
@@ -135,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

@@ -11,6 +11,7 @@ class CompilationOptions(val output: OutputType,
val zpAllowed: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val romable: Boolean,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
@@ -31,10 +32,11 @@ class CompilationOptions(val output: OutputType,
var breakpointCpuInstruction: String? = null,
var ignoreFootguns: Boolean = false,
var outputDir: Path = Path(""),
var quiet: Boolean = false,
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
compTarget.machine.initializeMemoryAreas(this)
compTarget.initializeMemoryAreas(this)
}
companion object {

View File

@@ -1,6 +1,6 @@
package prog8.code.core
import java.util.Objects
import java.util.*
enum class BaseDataType {
UBYTE, // pass by value 8 bits unsigned
@@ -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;
@@ -23,16 +26,20 @@ enum class BaseDataType {
this.isWord -> other.isByteOrBool
this == LONG -> other.isByteOrBool || other.isWord
this == STR && other == UWORD || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
fun equalsSize(other: BaseDataType) =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord
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
@@ -48,73 +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, 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 -> {
@@ -135,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()
}
@@ -147,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["
@@ -174,24 +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 {
if(isArray) throw IllegalArgumentException("cannot compare size of array types")
return base.largerSizeThan(other.base)
}
fun equalsSize(other: DataType): Boolean {
if(isArray) throw IllegalArgumentException("cannot compare size of array types")
return base.equalsSize(other.base)
}
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
@@ -207,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
@@ -273,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 {
@@ -321,7 +472,8 @@ val CpuRegisters = arrayOf(
enum class OutputType {
RAW,
PRG,
XEX
XEX,
LIBRARY
}
enum class CbmPrgLauncherType {

View File

@@ -1,8 +1,43 @@
package prog8.code.core
import java.nio.file.Path
enum class CpuType {
CPU6502,
CPU65C02,
VIRTUAL
}
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val BSSGOLDENRAM_START: UInt
val BSSGOLDENRAM_END: UInt
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
fun convertFloatToBytes(num: Double): List<UByte>
fun convertBytesToFloat(bytes: List<UByte>): Double
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean)
fun isIOAddress(address: UInt): Boolean
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String

View File

@@ -13,4 +13,6 @@ interface IErrorReporter {
}
fun noErrorForLine(position: Position): Boolean
fun printSingleError(errormessage: String)
}

View File

@@ -1,36 +0,0 @@
package prog8.code.core
import java.nio.file.Path
enum class CpuType {
CPU6502,
CPU65c02,
VIRTUAL
}
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
val BSSHIGHRAM_START: UInt
val BSSHIGHRAM_END: UInt
val BSSGOLDENRAM_START: UInt
val BSSGOLDENRAM_END: UInt
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
fun convertFloatToBytes(num: Double): List<UByte>
fun convertBytesToFloat(bytes: List<UByte>): Double
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isIOAddress(address: UInt): Boolean
}

View File

@@ -5,7 +5,7 @@ interface IMemSizer {
fun memorySize(dt: BaseDataType): Int {
if(dt.isPassByRef)
return memorySize(DataType.forDt(BaseDataType.UWORD), null) // a pointer size
return memorySize(DataType.UWORD, null) // a pointer size
try {
return memorySize(DataType.forDt(dt), null)
} catch (x: NoSuchElementException) {

View File

@@ -9,7 +9,8 @@ enum class Encoding(val prefix: String) {
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
CP437("cp437"), // cx16 (ibm pc, codepage 437)
KATAKANA("kata") // cx16 (katakana)
KATAKANA("kata"), // cx16 (katakana)
C64OS("c64os") // c64 (C64 OS)
}
interface IStringEncoding {

View File

@@ -22,7 +22,7 @@ abstract class MemoryAllocator(protected val options: CompilationOptions) {
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
abstract val SCRATCH_REG : UInt // temp storage for a register byte, must be B1+1
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
@@ -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)
@@ -82,7 +83,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
datatype.isFloat -> {
if (options.floats) {
val memsize = options.compTarget.memorySize(DataType.forDt(BaseDataType.FLOAT), null)
val memsize = options.compTarget.memorySize(DataType.FLOAT, null)
if(position!=null)
errors.warn("allocating a large value in zeropage; float $memsize bytes", position)
else
@@ -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")
}
}
@@ -133,8 +135,6 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
require(size>0)
return free.containsAll((address until address+size.toUInt()).toList())
}
abstract fun allocateCx16VirtualRegisters()
}
@@ -156,7 +156,7 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc
datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!)
datatype.isFloat -> {
if (options.floats) {
options.compTarget.memorySize(DataType.forDt(BaseDataType.FLOAT), null)
options.compTarget.memorySize(DataType.FLOAT, null)
} else return Err(MemAllocationError("floating point option not enabled"))
}
else -> throw MemAllocationError("weird dt")

View File

@@ -1,9 +1,9 @@
package prog8.code.core
import prog8.code.sanitize
import prog8.code.source.SourceCode
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
import kotlin.io.path.absolute
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
@@ -13,7 +13,7 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
if(SourceCode.isLibraryResource(file))
return "$file:$line:$startCol:"
return try {
val path = Path(file).absolute().normalize().toString()
val path = Path(file).sanitize().toString()
"file://$path:$line:$startCol:"
} catch(_: InvalidPathException) {
// this can occur on Windows when the source origin contains "invalid" characters such as ':'

View File

@@ -1,10 +1,10 @@
package prog8.code.source
import prog8.code.core.Position
import prog8.code.sanitize
import java.nio.file.Path
import java.util.TreeMap
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
// Resource caching "filesystem".
@@ -21,12 +21,12 @@ object ImportFileSystem {
fun expandTilde(path: Path): Path = Path(expandTilde(path.toString()))
fun getFile(path: Path): SourceCode {
val normalized = path.absolute().normalize()
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
val normalized = path.sanitize()
val cached = cache[normalized.toString()]
if (cached != null)
return cached
val file = SourceCode.File(normalized)
val file = SourceCode.File(normalized, isLibrary)
cache[normalized.toString()] = file
return file
}
@@ -48,7 +48,7 @@ object ImportFileSystem {
val cached = cache[position.file]
if(cached != null)
return getLine(cached, position.line)
val path = Path(position.file).absolute().normalize()
val path = Path(position.file).sanitize()
val cached2 = cache[path.toString()]
if(cached2 != null)
return getLine(cached2, position.line)

View File

@@ -1,5 +1,6 @@
package prog8.code.source
import prog8.code.sanitize
import java.io.IOException
import java.nio.file.Path
import java.text.Normalizer
@@ -22,6 +23,11 @@ sealed class SourceCode {
*/
abstract val isFromFilesystem: Boolean
/**
* Whether this [SourceCode] instance was created from a library module file
*/
abstract val isFromLibrary: Boolean
/**
* The logical name of the source code unit. Usually the module's name.
*/
@@ -54,7 +60,7 @@ sealed class SourceCode {
private const val LIBRARYFILEPREFIX = "library:"
private const val STRINGSOURCEPREFIX = "string:"
val curdir: Path = Path(".").absolute()
fun relative(path: Path): Path = curdir.relativize(path.absolute())
fun relative(path: Path): Path = curdir.relativize(path.sanitize())
fun isRegularFilesystemPath(pathString: String) = !isLibraryResource(pathString) && !isStringResource(pathString)
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
fun isStringResource(path: String) = path.startsWith(STRINGSOURCEPREFIX)
@@ -76,6 +82,7 @@ sealed class SourceCode {
override val text = origText.replace("\\R".toRegex(), "\n") // normalize line endings
override val isFromResources = false
override val isFromFilesystem = false
override val isFromLibrary = false
override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}"
override val name = "<unnamed-text>"
}
@@ -89,7 +96,7 @@ sealed class SourceCode {
* @throws NoSuchFileException if the file does not exist
* @throws FileSystemException if the file cannot be read
*/
internal class File(path: Path): SourceCode() {
internal class File(path: Path, override val isFromLibrary: Boolean): SourceCode() {
override val text: String
override val origin: String
override val name: String
@@ -120,6 +127,7 @@ sealed class SourceCode {
override val isFromResources = true
override val isFromFilesystem = false
override val isFromLibrary = true
override val origin = "$LIBRARYFILEPREFIX$normalized"
override val text: String
override val name: String
@@ -146,6 +154,7 @@ sealed class SourceCode {
class Generated(override val name: String) : SourceCode() {
override val isFromResources: Boolean = false
override val isFromFilesystem: Boolean = false
override val isFromLibrary: Boolean = false
override val origin: String = name
override val text: String = "<generated code node, no text representation>"
}

View File

@@ -1,40 +0,0 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.atari.AtariMachineDefinition
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val defaultEncoding = Encoding.ATASCII
companion object {
const val NAME = "atari"
}
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 * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.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

@@ -1,19 +1,81 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.c128.C128MachineDefinition
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val machine = C128MachineDefinition()
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
const val NAME = "c128"
}
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The c128 target only supports the main emulator (Vice).")
return
}
if(!quiet)
println("\nStarting C-128 emulator x128...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline)
if(!quiet)
processb.inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
}
}

View File

@@ -1,23 +1,95 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.c64.C64MachineDefinition
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import prog8.code.target.zp.C64Zeropage
import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val machine = C64MachineDefinition()
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
const val NAME = "c64"
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
}
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = 0xcfdfu
override val BSSGOLDENRAM_START = 0u // no golden ram on C64
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x64sc", "x64")) {
if(!quiet)
println("\nStarting C-64 emulator $emulator...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline)
if(!quiet)
processb.inheritIO()
val process: Process
try {
process=processb.start()
} catch(_: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
@@ -26,8 +98,6 @@ val CompilationTargets = listOf(
C128Target.NAME,
Cx16Target.NAME,
PETTarget.NAME,
AtariTarget.NAME,
Neo6502Target.NAME,
VMTarget.NAME
)
@@ -36,8 +106,6 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
Neo6502Target.NAME -> Neo6502Target()
else -> throw IllegalArgumentException("invalid compilation target")
}

View File

@@ -0,0 +1,171 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.source.ImportFileSystem.expandTilde
import prog8.code.target.encodings.Encoder
import prog8.code.target.zp.ConfigurableZeropage
import java.io.IOException
import java.nio.file.Path
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.inputStream
import kotlin.io.path.isDirectory
import kotlin.io.path.nameWithoutExtension
class ConfigFileTarget(
override val name: String,
override val defaultEncoding: Encoding,
override val cpu: CpuType,
override val PROGRAM_LOAD_ADDRESS: UInt,
override val PROGRAM_MEMTOP_ADDRESS: UInt,
override val STARTUP_CODE_RESERVED_SIZE: UInt,
override val BSSHIGHRAM_START: UInt,
override val BSSHIGHRAM_END: UInt,
override val BSSGOLDENRAM_START: UInt,
override val BSSGOLDENRAM_END: UInt,
override val defaultOutputType: OutputType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: List<String>,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
val zpScratchW1: UInt,
val zpScratchW2: UInt,
val virtualregistersStart: UInt,
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {
private fun Properties.getString(property: String): String {
val value = this.getProperty(property, null)
if(value!=null)
return value
throw NoSuchElementException("string property '$property' not found in config file")
}
private fun Properties.getInteger(property: String): UInt {
val value = this.getProperty(property, null)
if(value!=null) return parseInt(value)
throw NoSuchElementException("integer property '$property' not found in config file")
}
private fun parseInt(value: String): UInt {
if(value.startsWith("0x"))
return value.drop(2).toUInt(16)
if(value.startsWith("$"))
return value.drop(1).toUInt(16)
if(value.startsWith("%"))
return value.drop(1).toUInt(2)
return value.toUInt()
}
private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> {
val rangesStr = props.getString(key)
if(rangesStr.isBlank())
return emptyList()
val result = mutableListOf<UIntRange>()
val ranges = rangesStr.split(",").map { it.trim() }
for(r in ranges) {
if ('-' in r) {
val (fromStr, toStr) = r.split("-")
val from = parseInt(fromStr.trim())
val to = parseInt(toStr.trim())
result.add(from..to)
} else {
val address = parseInt(r)
result.add(address..address)
}
}
return result
}
fun fromConfigFile(configfile: Path): ConfigFileTarget {
val props = Properties()
props.load(configfile.inputStream())
val cpuString = props.getString("cpu").uppercase()
val cpuType = try {
CpuType.valueOf(cpuString)
} catch (_: IllegalArgumentException) {
CpuType.valueOf("CPU$cpuString")
}
val ioAddresses = parseAddressRanges("io_regions", props)
val zpFullsafe = parseAddressRanges("zp_fullsafe", props)
val zpKernalsafe = parseAddressRanges("zp_kernalsafe", props)
val zpBasicsafe = parseAddressRanges("zp_basicsafe", props)
val libraryPath = expandTilde(Path(props.getString("library")))
if(!libraryPath.isDirectory())
throw IOException("invalid library path: $libraryPath")
val customLauncherStr = props.getProperty("custom_launcher_code", null)
val customLauncher =
if(customLauncherStr?.isNotBlank()==true)
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
return ConfigFileTarget(
configfile.nameWithoutExtension,
Encoding.entries.first { it.prefix==props.getString("encoding") },
cpuType,
props.getInteger("load_address"),
props.getInteger("memtop"),
0u, // used only in a very specific error condition check in a certain scenario...
props.getInteger("bss_highram_start"),
props.getInteger("bss_highram_end"),
props.getInteger("bss_goldenram_start"),
props.getInteger("bss_goldenram_end"),
defaultOutputType,
libraryPath,
customLauncher,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),
props.getInteger("zp_scratch_w1"),
props.getInteger("zp_scratch_w2"),
props.getInteger("virtual_registers"),
zpFullsafe,
zpKernalsafe,
zpBasicsafe,
)
}
}
// TODO floats are not yet supported here, just enter some values
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 8
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam // TODO this is not yet used
override fun getFloatAsmBytes(num: Number) = TODO("floats")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("floats")
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
throw IllegalArgumentException("Custom compiler target cannot automatically launch an emulator. Do this manually.")
}
override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it }
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = ConfigurableZeropage(
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2,
virtualregistersStart,
zpBasicsafe,
zpKernalsafe,
zpFullsafe,
compilerOptions
)
// note: there's no golden ram yet
}
}

View File

@@ -1,19 +1,94 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.target.cx16.CX16MachineDefinition
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val machine = CX16MachineDefinition()
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
const val NAME = "cx16"
}
override val cpu = CpuType.CPU65C02
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
override val BSSGOLDENRAM_START = 0x0400u
override val BSSGOLDENRAM_END = 0x07ffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
val emulator: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulator = "x16emu"
extraArgs = listOf("-debug")
}
2 -> {
emulator = "box16"
extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString()))
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
return
}
}
if(!quiet)
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline)
if(!quiet)
processb.inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
}
}

View File

@@ -1,10 +1,9 @@
package prog8.code.target.cbm
package prog8.code.target
import prog8.code.core.InternalCompilerException
import kotlin.math.absoluteValue
import kotlin.math.pow
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte) {
companion object {
@@ -19,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

@@ -1,40 +0,0 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.neo6502.Neo6502MachineDefinition
class Neo6502Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = Neo6502MachineDefinition()
override val defaultEncoding = Encoding.ISO
companion object {
const val NAME = "neo"
}
override fun memorySize(dt: DataType, 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 * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.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

@@ -1,33 +1,38 @@
package prog8.code.target.cbm
package prog8.code.target
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
internal object CbmMemorySizer: 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
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * Mflpt5.FLOAT_MEM_SIZE
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
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 -> Mflpt5.FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isFloat -> floatsize * (numElements ?: 1)
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

@@ -1,19 +1,80 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.target.pet.PETMachineDefinition
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val machine = PETMachineDefinition()
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
const val NAME = "pet32"
}
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
override val BSSHIGHRAM_START = 0u
override val BSSHIGHRAM_END = 0u
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The pet target only supports the main emulator (Vice).")
return
}
if(!quiet)
println("\nStarting PET emulator...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline)
if(!quiet)
processb.inheritIO()
val process=processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}

View File

@@ -1,39 +1,107 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.virtual.VirtualMachineDefinition
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(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = VirtualMachineDefinition()
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
const val NAME = "virtual"
const val FLOAT_MEM_SIZE = 8 // 64-bits double
}
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 * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
override val cpu = CpuType.VIRTUAL
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.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)
}
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toDouble().toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { "$$it" }
return parts.joinToString(", ")
}
}
override fun convertFloatToBytes(num: Double): List<UByte> {
val bits = num.toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() }
return parts
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==8) { "need 8 bytes" }
val b0 = bytes[0].toLong() shl (8*7)
val b1 = bytes[1].toLong() shl (8*6)
val b2 = bytes[2].toLong() shl (8*5)
val b3 = bytes[3].toLong() shl (8*4)
val b4 = bytes[4].toLong() shl (8*3)
val b5 = bytes[5].toLong() shl (8*2)
val b6 = bytes[6].toLong() shl (8*1)
val b7 = bytes[7].toLong() shl (8*0)
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(!quiet)
println("\nStarting Virtual Machine...")
// 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 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
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: String, quiet: Boolean)
}
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
}

View File

@@ -1,62 +0,0 @@
package prog8.code.target.atari
import prog8.code.core.*
import java.nio.file.Path
class AtariMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x2000u
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // TODO what's memtop?
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val cmdline: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "atari800"
cmdline = listOf(emulatorName, "-xl", "-xl-rev", "2", "-nobasic", "-run", "${programNameWithPath}.xex")
}
2 -> {
emulatorName = "altirra"
cmdline = listOf("Altirra64.exe", "${programNameWithPath.normalize()}.xex")
}
else -> {
System.err.println("Atari target only supports atari800 and altirra emulators.")
return
}
}
// TODO monlist?
println("\nStarting Atari800XL emulator $emulatorName...")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}

View File

@@ -1,57 +0,0 @@
package prog8.code.target.atari
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xcbu // temp storage for a single byte
override val SCRATCH_REG = 0xccu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
init {
if (options.floats) {
throw InternalCompilerException("Atari target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
// TODO all atari usable zero page locations, except the ones used by the system's IRQ routine
free.addAll(0x00u..0xffu)
// TODO atari free.removeAll(arrayOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x80u..0xffu) // TODO
}
ZeropageType.BASICSAFE,
ZeropageType.FLOATSAFE -> {
free.addAll(0x80u..0xffu) // TODO
free.removeAll(0xd4u .. 0xefu) // floating point storage
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if atari can put the virtual regs in ZP")
}
}

View File

@@ -1,62 +0,0 @@
package prog8.code.target.c128
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class C128MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
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 lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c128 target only supports the main emulator (Vice).")
return
}
println("\nStarting C-128 emulator x128...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
}
}

View File

@@ -1,73 +0,0 @@
package prog8.code.target.c64
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.io.IOException
import java.nio.file.Path
class C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = 0xcfdfu
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(_: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0xc000u until 0xd000u)
}
}

View File

@@ -1,75 +0,0 @@
package prog8.code.target.cx16
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
override val BSSGOLDENRAM_START = 0x0400u
override val BSSGOLDENRAM_END = 0x07ffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulator: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulator = "x16emu"
extraArgs = listOf("-debug")
}
2 -> {
emulator = "box16"
extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString()))
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
return
}
}
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0x0400u until 0x0800u)
}
}

View File

@@ -26,7 +26,7 @@ object AtasciiEncoding {
'▖',
// $10
'♣',
'♣',
'┌',
'─',
'┼',
@@ -62,7 +62,7 @@ object AtasciiEncoding {
'/',
// $30
'0',
'0',
'1',
'2',
'3',
@@ -80,7 +80,7 @@ object AtasciiEncoding {
'?',
// $40
'@',
'@',
'A',
'B',
'C',
@@ -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

@@ -0,0 +1,327 @@
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
object C64osEncoding {
// decoding: from C64 OS Screencodes (0-255) to unicode
// character table from:
// https://www.c64os.com/c64os/usersguide/appendices#charactersets
private val decodingC64os = charArrayOf(
'@' , // @ 0x00 -> COMMERCIAL AT
'a' , // a 0x01 -> LATIN SMALL LETTER A
'b' , // b 0x02 -> LATIN SMALL LETTER B
'c' , // c 0x03 -> LATIN SMALL LETTER C
'd' , // d 0x04 -> LATIN SMALL LETTER D
'e' , // e 0x05 -> LATIN SMALL LETTER E
'f' , // f 0x06 -> LATIN SMALL LETTER F
'g' , // g 0x07 -> LATIN SMALL LETTER G
'h' , // h 0x08 -> LATIN SMALL LETTER H
'i' , // i 0x09 -> LATIN SMALL LETTER I
'j' , // j 0x0A -> LATIN SMALL LETTER J
'k' , // k 0x0B -> LATIN SMALL LETTER K
'l' , // l 0x0C -> LATIN SMALL LETTER L
'm' , // m 0x0D -> LATIN SMALL LETTER M
'n' , // n 0x0E -> LATIN SMALL LETTER N
'o' , // o 0x0F -> LATIN SMALL LETTER O
'p' , // p 0x10 -> LATIN SMALL LETTER P
'q' , // q 0x11 -> LATIN SMALL LETTER Q
'r' , // r 0x12 -> LATIN SMALL LETTER R
's' , // s 0x13 -> LATIN SMALL LETTER S
't' , // t 0x14 -> LATIN SMALL LETTER T
'u' , // u 0x15 -> LATIN SMALL LETTER U
'v' , // v 0x16 -> LATIN SMALL LETTER V
'w' , // w 0x17 -> LATIN SMALL LETTER W
'x' , // x 0x18 -> LATIN SMALL LETTER X
'y' , // y 0x19 -> LATIN SMALL LETTER Y
'z' , // z 0x1A -> LATIN SMALL LETTER Z
'[' , // [ 0x1B -> LEFT SQUARE BRACKET
'\\' , // \ 0x1C -> REVERSE SOLIDUS
']' , // ] 0x1D -> RIGHT SQUARE BRACKET
'^' , // ^ 0x1E -> CIRCUMFLEX
'_' , // _ 0x1F -> UNDERSCORE
' ' , // 0x20 -> SPACE
'!' , // ! 0x21 -> EXCLAMATION MARK
'"' , // " 0x22 -> QUOTATION MARK
'#' , // # 0x23 -> NUMBER SIGN
'$' , // $ 0x24 -> DOLLAR SIGN
'%' , // % 0x25 -> PERCENT SIGN
'&' , // & 0x26 -> AMPERSAND
'\'' , // ' 0x27 -> APOSTROPHE
'(' , // ( 0x28 -> LEFT PARENTHESIS
')' , // ) 0x29 -> RIGHT PARENTHESIS
'*' , // * 0x2A -> ASTERISK
'+' , // + 0x2B -> PLUS SIGN
',' , // , 0x2C -> COMMA
'-' , // - 0x2D -> HYPHEN-MINUS
'.' , // . 0x2E -> FULL STOP
'/' , // / 0x2F -> SOLIDUS
'0' , // 0 0x30 -> DIGIT ZERO
'1' , // 1 0x31 -> DIGIT ONE
'2' , // 2 0x32 -> DIGIT TWO
'3' , // 3 0x33 -> DIGIT THREE
'4' , // 4 0x34 -> DIGIT FOUR
'5' , // 5 0x35 -> DIGIT FIVE
'6' , // 6 0x36 -> DIGIT SIX
'7' , // 7 0x37 -> DIGIT SEVEN
'8' , // 8 0x38 -> DIGIT EIGHT
'9' , // 9 0x39 -> DIGIT NINE
':' , // : 0x3A -> COLON
';' , // ; 0x3B -> SEMICOLON
'<' , // < 0x3C -> LESS-THAN SIGN
'=' , // = 0x3D -> EQUALS SIGN
'>' , // > 0x3E -> GREATER-THAN SIGN
'?' , // ? 0x3F -> QUESTION MARK
'`' , // ` 0x40 -> GRAVE ACCENT
'A' , // A 0x41 -> LATIN CAPITAL LETTER A
'B' , // B 0x42 -> LATIN CAPITAL LETTER B
'C' , // C 0x43 -> LATIN CAPITAL LETTER C
'D' , // D 0x44 -> LATIN CAPITAL LETTER D
'E' , // E 0x45 -> LATIN CAPITAL LETTER E
'F' , // F 0x46 -> LATIN CAPITAL LETTER F
'G' , // G 0x47 -> LATIN CAPITAL LETTER G
'H' , // H 0x48 -> LATIN CAPITAL LETTER H
'I' , // I 0x49 -> LATIN CAPITAL LETTER I
'J' , // J 0x4A -> LATIN CAPITAL LETTER J
'K' , // K 0x4B -> LATIN CAPITAL LETTER K
'L' , // L 0x4C -> LATIN CAPITAL LETTER L
'M' , // M 0x4D -> LATIN CAPITAL LETTER M
'N' , // N 0x4E -> LATIN CAPITAL LETTER N
'O' , // O 0x4F -> LATIN CAPITAL LETTER O
'P' , // P 0x50 -> LATIN CAPITAL LETTER P
'Q' , // Q 0x51 -> LATIN CAPITAL LETTER Q
'R' , // R 0x52 -> LATIN CAPITAL LETTER R
'S' , // S 0x53 -> LATIN CAPITAL LETTER S
'T' , // T 0x54 -> LATIN CAPITAL LETTER T
'U' , // U 0x55 -> LATIN CAPITAL LETTER U
'V' , // V 0x56 -> LATIN CAPITAL LETTER V
'W' , // W 0x57 -> LATIN CAPITAL LETTER W
'X' , // X 0x58 -> LATIN CAPITAL LETTER X
'Y' , // Y 0x59 -> LATIN CAPITAL LETTER Y
'Z' , // Z 0x5A -> LATIN CAPITAL LETTER Z
'{' , // { 0x5B -> LEFT BRACE
'|' , // | 0x5C -> VERTICAL BAR
'}' , // } 0x5D -> RIGHT BRACE
'~' , // ~ 0x5E -> TILDE
'\ufffe', // 0x5F -> RESERVED
'\u00a0', // 0x60 -> NO-BREAK SPACE (TRANSPARENT)
'\ufffe', // 0x61 -> COMMODORE SYMBOL
'\u2191', // ↑ 0x62 -> UP ARROW
'\u2193', // ↓ 0x63 -> DOWN ARROW
'\u2190', // ← 0x64 -> LEFT ARROW
'\u2192', // → 0x65 -> RIGHT ARROW
'\u231A', // ⌚ 0x66 -> WATCH (ANALOG CLOCKFACE)
'\u21BB', // ↻ 0x67 -> CYCLE ARROWS
'\u2026', // … 0x68 -> ELLIPSIS
'\u25a7', // ▧ 0x69 -> DIAGNONAL STRIPES
'\u2610', // ☐ 0x6A -> CHECKBOX UNCHECKED
'\u2611', // ☑ 0x6B -> CHECKBOX CHECKED
'\ufffe', // 0x6C -> RADIO BUTTON UNSELECTED
'\ufffe', // 0x6D -> RADIO BUTTON SELECTED
'\ufffe', // 0x6E -> UTILITY CLOSE BUTTON
'\ufffe', // 0x6F -> UTILITY TITLE BAR
'\u00a9', // © 0x70 -> COPYRIGHT
'\u2713', // ✓ 0x71 -> CHECKMARK
'\u2261', // ≡ 0x72 -> THREE HORIZONTAL STRIPES
'\ufffe', // 0x73 -> TICK TRACK
'\ufffe', // 0x74 -> TICK TRACK NUB
'\ufffe', // 0x75 -> TAB CORNER
'\u2980', // ⦀ 0x76 -> THREE VERTICAL STRIPES
'\ufffe', // 0x77 -> CUSTOM 1
'\ufffe', // 0x78 -> CUSTOM 2
'\ufffe', // 0x79 -> CUSTOM 3
'\ufffe', // 0x7A -> CUSTOM 4
'\ufffe', // 0x7B -> CUSTOM 5
'\ufffe', // 0x7C -> CUSTOM 6
'\ufffe', // 0x7D -> CUSTOM 7
'\ufffe', // 0x7E -> CUSTOM 8
'\ufffe', // 0x7F -> CUSTOM 9
'\ufffe', // 0x80 -> REVERSED COMMERCIAL AT
'\ufffe', // 0x81 -> REVERSED LATIN SMALL LETTER A
'\ufffe', // 0x82 -> REVERSED LATIN SMALL LETTER B
'\ufffe', // 0x83 -> REVERSED LATIN SMALL LETTER C
'\ufffe', // 0x84 -> REVERSED LATIN SMALL LETTER D
'\ufffe', // 0x85 -> REVERSED LATIN SMALL LETTER E
'\ufffe', // 0x86 -> REVERSED LATIN SMALL LETTER F
'\ufffe', // 0x87 -> REVERSED LATIN SMALL LETTER G
'\ufffe', // 0x88 -> REVERSED LATIN SMALL LETTER H
'\ufffe', // 0x89 -> REVERSED LATIN SMALL LETTER I
'\ufffe', // 0x8A -> REVERSED LATIN SMALL LETTER J
'\ufffe', // 0x8B -> REVERSED LATIN SMALL LETTER K
'\ufffe', // 0x8C -> REVERSED LATIN SMALL LETTER L
'\ufffe', // 0x8D -> REVERSED LATIN SMALL LETTER M
'\ufffe', // 0x8E -> REVERSED LATIN SMALL LETTER N
'\ufffe', // 0x8F -> REVERSED LATIN SMALL LETTER O
'\ufffe', // 0x90 -> REVERSED LATIN SMALL LETTER P
'\ufffe', // 0x91 -> REVERSED LATIN SMALL LETTER Q
'\ufffe', // 0x92 -> REVERSED LATIN SMALL LETTER R
'\ufffe', // 0x93 -> REVERSED LATIN SMALL LETTER S
'\ufffe', // 0x94 -> REVERSED LATIN SMALL LETTER T
'\ufffe', // 0x95 -> REVERSED LATIN SMALL LETTER U
'\ufffe', // 0x96 -> REVERSED LATIN SMALL LETTER V
'\ufffe', // 0x97 -> REVERSED LATIN SMALL LETTER W
'\ufffe', // 0x98 -> REVERSED LATIN SMALL LETTER X
'\ufffe', // 0x99 -> REVERSED LATIN SMALL LETTER Y
'\ufffe', // 0x9A -> REVERSED LATIN SMALL LETTER Z
'\ufffe', // 0x9B -> REVERSED LEFT SQUARE BRACKET
'\ufffe', // 0x9C -> REVERSED REVERSE SOLIDUS
'\ufffe', // 0x9D -> REVERSED RIGHT SQUARE BRACKET
'\ufffe', // 0x9E -> REVERSED CIRCUMFLEX
'\ufffe', // 0x9F -> REVERSED UNDERSCORE
'\ufffe', // 0xA0 -> REVERSED SPACE
'\ufffe', // 0xA1 -> REVERSED EXCLAMATION MARK
'\ufffe', // 0xA2 -> REVERSED QUOTATION MARK
'\ufffe', // 0xA3 -> REVERSED NUMBER SIGN
'\ufffe', // 0xA4 -> REVERSED DOLLAR SIGN
'\ufffe', // 0xA5 -> REVERSED PERCENT SIGN
'\ufffe', // 0xA6 -> REVERSED AMPERSAND
'\ufffe', // 0xA7 -> REVERSED APOSTROPHE
'\ufffe', // 0xA8 -> REVERSED LEFT PARENTHESIS
'\ufffe', // 0xA9 -> REVERSED RIGHT PARENTHESIS
'\ufffe', // 0xAA -> REVERSED ASTERISK
'\ufffe', // 0xAB -> REVERSED PLUS SIGN
'\ufffe', // 0xAC -> REVERSED COMMA
'\ufffe', // 0xAD -> REVERSED HYPHEN-MINUS
'\ufffe', // 0xAE -> REVERSED FULL STOP
'\ufffe', // 0xAF -> REVERSED SOLIDUS
'\ufffe', // 0xB0 -> REVERSED DIGIT ZERO
'\ufffe', // 0xB1 -> REVERSED DIGIT ONE
'\ufffe', // 0xB2 -> REVERSED DIGIT TWO
'\ufffe', // 0xB3 -> REVERSED DIGIT THREE
'\ufffe', // 0xB4 -> REVERSED DIGIT FOUR
'\ufffe', // 0xB5 -> REVERSED DIGIT FIVE
'\ufffe', // 0xB6 -> REVERSED DIGIT SIX
'\ufffe', // 0xB7 -> REVERSED DIGIT SEVEN
'\ufffe', // 0xB8 -> REVERSED DIGIT EIGHT
'\ufffe', // 0xB9 -> REVERSED DIGIT NINE
'\ufffe', // 0xBA -> REVERSED COLON
'\ufffe', // 0xBB -> REVERSED SEMICOLON
'\ufffe', // 0xBC -> REVERSED LESS-THAN SIGN
'\ufffe', // 0xBD -> REVERSED EQUALS SIGN
'\ufffe', // 0xBE -> REVERSED GREATER-THAN SIGN
'\ufffe', // 0xBF -> REVERSED QUESTION MARK
'\ufffe', // 0xC0 -> REVERSED GRAVE ACCENT
'\ufffe', // 0xC1 -> REVERSED LATIN CAPITAL LETTER A
'\ufffe', // 0xC2 -> REVERSED LATIN CAPITAL LETTER B
'\ufffe', // 0xC3 -> REVERSED LATIN CAPITAL LETTER C
'\ufffe', // 0xC4 -> REVERSED LATIN CAPITAL LETTER D
'\ufffe', // 0xC5 -> REVERSED LATIN CAPITAL LETTER E
'\ufffe', // 0xC6 -> REVERSED LATIN CAPITAL LETTER F
'\ufffe', // 0xC7 -> REVERSED LATIN CAPITAL LETTER G
'\ufffe', // 0xC8 -> REVERSED LATIN CAPITAL LETTER H
'\ufffe', // 0xC9 -> REVERSED LATIN CAPITAL LETTER I
'\ufffe', // 0xCA -> REVERSED LATIN CAPITAL LETTER J
'\ufffe', // 0xCB -> REVERSED LATIN CAPITAL LETTER K
'\ufffe', // 0xCC -> REVERSED LATIN CAPITAL LETTER L
'\ufffe', // 0xCD -> REVERSED LATIN CAPITAL LETTER M
'\ufffe', // 0xCE -> REVERSED LATIN CAPITAL LETTER N
'\ufffe', // 0xCF -> REVERSED LATIN CAPITAL LETTER O
'\ufffe', // 0xD0 -> REVERSED LATIN CAPITAL LETTER P
'\ufffe', // 0xD1 -> REVERSED LATIN CAPITAL LETTER Q
'\ufffe', // 0xD2 -> REVERSED LATIN CAPITAL LETTER R
'\ufffe', // 0xD3 -> REVERSED LATIN CAPITAL LETTER S
'\ufffe', // 0xD4 -> REVERSED LATIN CAPITAL LETTER T
'\ufffe', // 0xD5 -> REVERSED LATIN CAPITAL LETTER U
'\ufffe', // 0xD6 -> REVERSED LATIN CAPITAL LETTER V
'\ufffe', // 0xD7 -> REVERSED LATIN CAPITAL LETTER W
'\ufffe', // 0xD8 -> REVERSED LATIN CAPITAL LETTER X
'\ufffe', // 0xD9 -> REVERSED LATIN CAPITAL LETTER Y
'\ufffe', // 0xDA -> REVERSED LATIN CAPITAL LETTER Z
'\ufffe', // 0xDB -> REVERSED LEFT BRACE
'\ufffe', // 0xDC -> REVERSED VERTICAL BAR
'\ufffe', // 0xDD -> REVERSED RIGHT BRACE
'\ufffe', // 0xDE -> REVERSED TILDE
'\ufffe', // 0xDF -> RESERVED
'\ufffe', // 0xE0 -> RESERVED
'\ufffe', // 0xE1 -> REVERSED COMMODORE SYMBOL
'\ufffe', // 0xE2 -> REVERSED UP ARROW
'\ufffe', // 0xE3 -> REVERSED DOWN ARROW
'\ufffe', // 0xE4 -> REVERSED LEFT ARROW
'\ufffe', // 0xE5 -> REVERSED RIGHT ARROW
'\ufffe', // 0xE6 -> REVERSED ANALOG CLOCKFACE
'\ufffe', // 0xE7 -> REVERSED CYCLE ARROWS
'\ufffe', // 0xE8 -> REVERSED ELLIPSIS
'\ufffe', // 0xE9 -> REVERSED DIAGONAL STRIPES
'\ufffe', // 0xEA -> REVERSED CHECKBOX UNCHECKED
'\ufffe', // 0xEB -> REVERSED CHECKBOX CHECKED
'\ufffe', // 0xEC -> REVERSED RADIO BUTTON UNSELECTED
'\ufffe', // 0xED -> REVERSED RADIO BUTTON SELECTED
'\ufffe', // 0xEE -> MEMORY CHIP ICON
'\u21e7', // ⇧ 0xEF -> SHIFT SYMBOL
'\ufffe', // 0xF0 -> REVERSED COPYRIGHT SYMBOL
'\ufffe', // 0xF1 -> REVERSED CHECKMARK
'\ufffe', // 0xF2 -> REVERSED THREE HORIZONTAL STRIPES
'\ufffe', // 0xF3 -> REVERSED TICK TRACK
'\ufffe', // 0xF4 -> REVERSED TICK TRACK NUB
'\ufffe', // 0xF5 -> REVERSED TAB CORNER
'\ufffe', // 0xF6 -> REVERSED THREE VERTICAL STRIPES
'\ufffe', // 0xF7 -> CUSTOM 10
'\ufffe', // 0xF8 -> CUSTOM 11
'\ufffe', // 0xF9 -> CUSTOM 12
'\ufffe', // 0xFA -> CUSTOM 13
'\ufffe', // 0xFB -> CUSTOM 14
'\ufffe', // 0xFC -> CUSTOM 15
'\ufffe', // 0xFD -> CUSTOM 16
'\ufffe', // 0xFE -> CUSTOM 17
'\ufffe' // 0xFF -> CUSTOM 18
)
// encoding: from unicode to C64 OS Screencodes (0-255)
private val encodingC64os = decodingC64os.withIndex().associate{it.value to it.index}
private fun replaceSpecial(chr: Char): Char =
when(chr) {
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
else -> chr
}
fun encode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
val chr = replaceSpecial(chr3)
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()
}
else -> {
if(chr.isISOControl())
throw CharConversionException("no c64os character for char #${chr.code}")
else
throw CharConversionException("no c64os character for char #${chr.code} '${chr}'")
}
}
}
return try {
Ok(text.map {
try {
encodeChar(it, lowercase)
} catch (x: CharConversionException) {
encodeChar(it, !lowercase)
}
})
} catch(cx: CharConversionException) {
Err(cx)
}
}
fun decode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
return try {
Ok(screencode.map {
val code = it.toInt()
if(code<0 || code>= decodingC64os.size)
throw CharConversionException("c64os $code out of range 0..${decodingC64os.size-1}")
decodingC64os[code]
}.joinToString(""))
} catch(ce: CharConversionException) {
Err(ce)
}
}
}

View File

@@ -1,25 +1,24 @@
package prog8.code.target
package prog8.code.target.encodings
import com.github.michaelbull.result.fold
import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
import prog8.code.target.encodings.*
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")
}
return coded.fold(
@@ -31,12 +30,13 @@ 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")
}
return decoded.fold(
@@ -44,4 +44,4 @@ object Encoder: IStringEncoding {
success = { it }
)
}
}
}

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

@@ -1,50 +0,0 @@
package prog8.code.target.neo6502
import prog8.code.core.*
import java.nio.file.Path
class Neo6502MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0800u
override val PROGRAM_MEMTOP_ADDRESS = 0xfc00u // kernal starts here
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The neo target only supports the main emulator (neo).")
return
}
val cmdline = listOf("neo", "${programNameWithPath}.bin@800", "cold")
println("\nStarting Neo6502 emulator...")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xff00u..0xff0fu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = Neo6502Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}

View File

@@ -1,48 +0,0 @@
package prog8.code.target.neo6502
import prog8.code.core.*
class Neo6502Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xfau // temp storage for a single byte
override val SCRATCH_REG = 0xfbu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfcu // temp storage 1 for a word $fc+$fd
override val SCRATCH_W2 = 0xfeu // temp storage 2 for a word $fe+$ff
init {
if (options.floats) {
throw InternalCompilerException("Neo6502 target doesn't support floating point routines")
}
when (options.zeropage) {
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> {
free.addAll(0x22u..0xffu)
}
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.UWORD), 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.WORD), 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sH .. cx16.r15sH
}
}
}

View File

@@ -1,63 +0,0 @@
package prog8.code.target.pet
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class PETMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
override val BSSHIGHRAM_START = 0u
override val BSSHIGHRAM_END = 0u
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The pet target only supports the main emulator (Vice).")
return
}
println("\nStarting PET emulator...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process=processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
// there's no golden ram.
}
}

View File

@@ -1,93 +0,0 @@
package prog8.code.target.virtual
import prog8.code.core.*
import java.nio.file.Path
import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VirtualMachineDefinition: IMachineDefinition {
override val cpu = CpuType.VIRTUAL
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 8 // 64-bits double
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toDouble().toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
override fun convertFloatToBytes(num: Double): List<UByte> {
val bits = num.toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() }
return parts
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==8) { "need 8 bytes" }
val b0 = bytes[0].toLong() shl (8*7)
val b1 = bytes[1].toLong() shl (8*6)
val b2 = bytes[2].toLong() shl (8*5)
val b3 = bytes[3].toLong() shl (8*4)
val b4 = bytes[4].toLong() shl (8*3)
val b5 = bytes[5].toLong() shl (8*2)
val b6 = bytes[6].toLong() shl (8*1)
val b7 = bytes[7].toLong() shl (8*0)
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
println("\nStarting Virtual Machine...")
// 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())
} else {
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText())
else
throw NoSuchFileException(withExt.toFile(), reason="not a .p8ir file")
}
}
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: String)
}
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
}

View File

@@ -1,4 +1,4 @@
package prog8.code.target.c128
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
@@ -11,17 +11,11 @@ import prog8.code.core.ZeropageType
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x74u // temp storage for a single byte
override val SCRATCH_REG = 0x75u // temp storage for a register, must be B1+1
override val SCRATCH_REG = 0x75u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
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,
@@ -71,8 +65,4 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if C128 can put the virtual regs in ZP")
}
}

View File

@@ -1,4 +1,4 @@
package prog8.code.target.c64
package prog8.code.target.zp
import prog8.code.core.*
@@ -6,7 +6,7 @@ import prog8.code.core.*
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
@@ -79,18 +79,18 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.forDt(BaseDataType.UWORD), 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.forDt(BaseDataType.WORD), 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt())
}

View File

@@ -1,4 +1,4 @@
package prog8.code.target.cx16
package prog8.code.target.zp
import prog8.code.core.*
@@ -6,7 +6,7 @@ import prog8.code.core.*
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_REG = 0x7bu // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
@@ -52,17 +52,17 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
}
}
override fun allocateCx16VirtualRegisters() {
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.UWORD), 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.WORD), 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.forDt(BaseDataType.UBYTE), 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.forDt(BaseDataType.BYTE), 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}

View File

@@ -0,0 +1,61 @@
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
class ConfigurableZeropage(
override val SCRATCH_B1: UInt, // temp storage for a single byte
override val SCRATCH_REG: UInt, // temp storage for a register byte, must be B1+1
override val SCRATCH_W1: UInt, // temp storage 1 for a word
override val SCRATCH_W2: UInt, // temp storage 2 for a word
val virtualRegistersStart: UInt, // location of 32 bytes for the r0-r15 virtual registers
basicsafe: List<UIntRange>,
kernalsafe: List<UIntRange>,
fullsafe: List<UIntRange>,
options: CompilationOptions
) : Zeropage(options) {
init {
if (options.floats) {
TODO("floats in configurable target zp")
}
if(SCRATCH_REG!=SCRATCH_B1+1u)
throw IllegalArgumentException("Zero page scratch variable REG should be B1+1")
when (options.zeropage) {
ZeropageType.DONTUSE -> { /* don't use any zeropage at all */ }
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 in configurable target zp")
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
val address = virtualRegistersStart + (2*reg).toUInt()
if(address<=0xffu) {
allocatedVariables["cx16.r${reg}"] = VarAllocation(address, DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation(address, DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation(address, DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation(address+1u, DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation(address, DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation(address+1u, DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}
}

View File

@@ -1,4 +1,4 @@
package prog8.code.target.pet
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
@@ -11,15 +11,11 @@ import prog8.code.core.ZeropageType
class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xb3u // temp storage for a single byte
override val SCRATCH_REG = 0xb4u // temp storage for a register, must be B1+1
override val SCRATCH_REG = 0xb4u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
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,
@@ -52,8 +48,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if PET can put the virtual regs in ZP")
}
}

View File

@@ -1,19 +1,16 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0")
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 {
@@ -43,4 +40,4 @@ tasks.test {
testLogging {
events("skipped", "failed")
}
}
}

View File

@@ -11,8 +11,8 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<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>

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,16 @@
package prog8.codegen.cpu6502
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.ast.PtLabel
import prog8.code.core.IMachineDefinition
import prog8.code.core.ICompilationTarget
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int {
var numberOfOptimizations = 0
@@ -123,7 +123,7 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
private fun optimizeSameAssignments(
linesByFourteen: Sequence<List<IndexedValue<String>>>,
machine: IMachineDefinition,
machine: ICompilationTarget,
symbolTable: SymbolTable
): List<Modification> {
@@ -362,7 +362,7 @@ or *_afterif labels.
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
*/
val autoLabelPrefix = PtLabel.GENERATED_LABEL_PREFIX
val autoLabelPrefix = GENERATED_LABEL_PREFIX
if(first=="beq +" && second=="lda #1" && third=="+") {
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
@@ -386,7 +386,7 @@ This gets generated after certain if conditions, and only the branch instruction
private fun optimizeStoreLoadSame(
linesByFour: Sequence<List<IndexedValue<String>>>,
machine: IMachineDefinition,
machine: ICompilationTarget,
symbolTable: SymbolTable
): List<Modification> {
val mods = mutableListOf<Modification>()
@@ -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 {
@@ -459,7 +459,7 @@ private fun optimizeStoreLoadSame(
return mods
}
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_.$]*)""")
private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
// try to get the constant value address, could return null if it's a symbol instead
@@ -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))
}
}
/*
@@ -687,18 +701,20 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
// phy + ldy + pla -> tya + ldy
// phx + ldx + pla -> txa + ldx
// pha + lda + pla -> nop
if(first=="phy" && second.startsWith("ldy ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " tya"))
}
else if(first=="phx" && second.startsWith("ldx ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " txa"))
}
else if(first=="pha" && second.startsWith("lda ") && third=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
when (first) {
"phy" if second.startsWith("ldy ") && third=="pla" -> {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " tya"))
}
"phx" if second.startsWith("ldx ") && third=="pla" -> {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " txa"))
}
"pha" if second.startsWith("lda ") && third=="pla" -> {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
}

View File

@@ -1,10 +1,14 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtLabel
import prog8.code.core.*
import prog8.code.target.AtariTarget
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.IAssemblyProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.OutputType
import prog8.code.target.C128Target
import prog8.code.target.C64Target
import prog8.code.target.Neo6502Target
import prog8.code.target.PETTarget
import java.nio.file.Path
@@ -19,21 +23,25 @@ internal class AssemblyProgram(
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
private val listFile = outputDir.resolve("$name.list")
private val targetWithoutBreakpointsForEmulator = arrayOf(AtariTarget.NAME, Neo6502Target.NAME)
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
val assemblerCommand: List<String>
when (compTarget.name) {
in arrayOf("c64", "c128", "cx16", "pet32") -> {
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.
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", // "-Wno-strict-bool", "-Werror",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile"
)
val command = mutableListOf("64tass", "--cbm-prg", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
if(options.warnSymbolShadowing)
command.add("-Wshadow")
@@ -44,34 +52,59 @@ internal class AssemblyProgram(
command.add("--quiet")
if(options.asmListfile) {
command.addAll(listOf("--list=$listFile", "--no-monitor"))
command.add("--list=$listFile")
}
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${compTarget.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
if(!options.quiet)
println("\nCreating prg for target ${compTarget.name}.")
}
"atari" -> {
OutputType.XEX -> {
// Atari800XL .xex generation.
// TODO are these options okay for atari?
val command = mutableListOf("64tass", "--atari-xex", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
if(!options.quiet)
println("\nCreating xex for target ${compTarget.name}.")
}
OutputType.RAW -> {
// Neo6502/headerless raw program generation.
val command = mutableListOf("64tass", "--nostart", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
if(!options.quiet)
println("\nCreating raw binary for target ${compTarget.name}.")
}
OutputType.LIBRARY -> {
// CBM machines library (.bin) generation (with or without 2 byte load address header depending on the compilation target machine)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", // "-Werror", "-Wno-strict-bool"
"--no-monitor"
)
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
if(options.warnSymbolShadowing)
command.add("-Wshadow")
@@ -84,63 +117,26 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.XEX -> {
command.add("--atari-xex")
println("\nCreating xex for target ${compTarget.name}.")
xexFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
if(compTarget.name in listOf(C64Target.NAME, C128Target.NAME, PETTarget.NAME)) {
if(!options.quiet)
println("\nCreating binary library file with header for target ${compTarget.name}.")
command.add("--cbm-prg")
} else {
if(!options.quiet)
println("\nCreating binary library file without header for target ${compTarget.name}.")
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", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
"neo" -> {
// Neo6502 raw program generation.
if(options.output!=OutputType.RAW || options.loadAddress!=0x0800u || options.launcher!=CbmPrgLauncherType.NONE) {
throw AssemblyError("invalid program compilation options. Neo6502 requires %output raw, %launcher none, %address $0800")
}
// TODO are these options okay for neo?
val command = mutableListOf("64tass", "--case-sensitive", "--long-branch",
"-Wall", // "-Werror", "-Wno-strict-bool"
"--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type, need 'raw'")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
else -> throw AssemblyError("invalid compilation target")
}
val proc = ProcessBuilder(assemblerCommand).inheritIO().start()
val result = proc.waitFor()
if (result == 0 && compTarget.name !in targetWithoutBreakpointsForEmulator) {
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()
val process = proc.start()
val result = process.waitFor()
if (result == 0) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
@@ -148,7 +144,7 @@ internal class AssemblyProgram(
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${PtLabel.GENERATED_LABEL_PREFIX}.+?""")
val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
@@ -165,7 +161,7 @@ internal class AssemblyProgram(
for (line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line)
if (match != null)
breakpoints.add("break \$" + match.groupValues[1])
breakpoints.add("break $" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; breakpoint list now follows")

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,21 +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.forDt(BaseDataType.UBYTE), fcall.definingISub(), fcall.position, memory=memread)
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_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_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)
@@ -109,8 +111,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_math.divmod_ub_asm")
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingISub(), fcall.args[2].position, var2name)
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingISub(), fcall.args[3].position, var3name)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[3].position, var3name)
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false, false)
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false, false)
}
@@ -122,7 +124,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
// output: P8ZP_SCRATCH_W2 in ZP: 16-bit remainder, A/Y: 16 bit division result
asmgen.out(" jsr prog8_math.divmod_uw_asm")
val var2name = asmgen.asmVariableName(fcall.args[2] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingISub(), fcall.args[2].position, var2name)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
val remainderVar = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
assignAsmGen.assignRegisterpairWord(divisionTarget, RegisterOrPair.AY)
asmgen.out("""
@@ -141,7 +143,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcRsave() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
if (asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
php
pha
@@ -161,7 +163,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcRrestore() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
if (asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
plx
ply
@@ -198,7 +200,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
return
}
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.forDt(BaseDataType.UWORD)) // jump address
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
@@ -229,6 +231,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
if(asmgen.options.romable)
TODO("no code for non-const callfar (jsrfar) yet that's usable in ROM ${fcall.position}")
// self-modifying code: set jsrfar arguments
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
@@ -285,6 +290,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
if(asmgen.options.romable)
TODO("no code for non-const callfar2 (jsrfar) yet that's usable in ROM ${fcall.position}")
// self-modifying code: set jsrfar arguments
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
@@ -348,7 +356,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is PtBool -> TODO("word compare against bool")
is PtBool -> TODO("word compare against bool ${arg2.position}")
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
@@ -376,16 +384,32 @@ 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.forDt(BaseDataType.UWORD), fcall.position)
val addressOf = PtAddressOf(fcall.position)
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, 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.forDt(BaseDataType.UWORD), expression = addressOf)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
val assign = AsmAssignment(src, listOf(target), program.memsizer, fcall.position)
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) {
@@ -411,14 +435,17 @@ 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)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #\$80 |+ | sta ${varname},x")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #$80 |+ | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
@@ -426,7 +453,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
asmgen.out(" lda $variable | lsr a | bcc + | ora #$80 |+ | sta $variable")
}
else -> throw AssemblyError("weird type")
}
@@ -434,16 +461,18 @@ 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 |+ ")
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
else
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #\$80 | sta ${varname}+1,x |+ ")
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #$80 | sta ${varname}+1,x |+ ")
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #$80 | sta $variable+1 |+ ")
}
else -> throw AssemblyError("weird type")
}
@@ -461,33 +490,26 @@ 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 -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" ror ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.out(" php")
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.restoreRegisterStack(CpuRegister.X, false)
asmgen.out("""
plp
+ ror ${'$'}ffff,x ; modified""")
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
if(!what.address.isSimple()) asmgen.out(" plp")
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff ; modified""")
when {
what.address is PtNumber -> {
val number = (what.address as PtNumber).number
asmgen.out(" ror ${number.toHex()}")
}
what.address is PtIdentifier -> {
asmgen.out(" php") // save Carry
val sourceName = asmgen.loadByteFromPointerIntoA(what.address as PtIdentifier)
asmgen.out(" plp | ror a")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
else -> {
TODO("ror ptr-expression ${what.position}")
}
}
}
@@ -504,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
@@ -527,14 +551,16 @@ 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)
asmgen.out(" lda ${varname},x | cmp #\$80 | rol a | sta ${varname},x")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
}
is PtMemoryByte -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
asmgen.out(" lda ${number.toHex()} | cmp #$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
@@ -542,7 +568,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
asmgen.out(" lda $variable | cmp #$80 | rol a | sta $variable")
}
else -> throw AssemblyError("weird type")
}
@@ -551,9 +577,11 @@ 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 |+")
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
else
asmgen.out(" asl ${varname},x | rol ${varname}+1,x | bcc + | inc ${varname},x |+ ")
}
@@ -577,33 +605,25 @@ 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 -> {
if (what.address is PtNumber) {
val number = (what.address as PtNumber).number
asmgen.out(" rol ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
if(ptrAndIndex!=null) {
asmgen.out(" php")
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
asmgen.restoreRegisterStack(CpuRegister.X, false)
asmgen.out("""
plp
+ rol ${'$'}ffff,x ; modified""")
} else {
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
if(!what.address.isSimple()) asmgen.out(" plp")
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff ; modified""")
when {
what.address is PtNumber -> {
val number = (what.address as PtNumber).number
asmgen.out(" rol ${number.toHex()}")
}
what.address is PtIdentifier -> {
asmgen.out(" php") // save Carry
val sourceName = asmgen.loadByteFromPointerIntoA(what.address as PtIdentifier)
asmgen.out(" plp | rol a")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
else -> {
TODO("rol ptr-expression ${what.position}")
}
}
}
@@ -620,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
@@ -642,14 +664,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when(fcall.args[0]) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingSub(), fcall.position, variableAsmName = varname)
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
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
@@ -684,7 +706,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
multipliedIndex.parent=indexer
}
}
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingSub(), fcall.position, array = indexer)
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
}
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
}
@@ -692,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)
}
}
@@ -755,8 +777,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.forDt(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("""
pha
@@ -770,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 -> {
@@ -783,7 +857,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
sta ($varname)
txa
@@ -831,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.forDt(BaseDataType.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")
}
}
@@ -849,7 +965,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
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)) {
if (asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
ldy #1
lda ($varname),y
@@ -1035,38 +1151,58 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMkword(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
if(!needAsave) {
var needAsaveForArg0 = asmgen.needAsaveForExpr(fcall.args[0])
if(!needAsaveForArg0) {
val mr0 = fcall.args[0] as? PtMemoryByte
val mr1 = fcall.args[1] as? PtMemoryByte
if (mr0 != null)
needAsave = mr0.address !is PtNumber
needAsaveForArg0 = mr0.address !is PtNumber
if (mr1 != null)
needAsave = needAsave or (mr1.address !is PtNumber)
needAsaveForArg0 = needAsaveForArg0 or (mr1.address !is PtNumber)
}
when(reg) {
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
if(needAsave)
asmgen.out(" pla")
if(needAsaveForArg0 && !asmgen.needAsaveForExpr(fcall.args[1])) {
// first 0 then 1
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
} else if(!needAsaveForArg0 && asmgen.needAsaveForExpr(fcall.args[1])) {
// first 1 then 0
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if (needAsaveForArg0)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
if (needAsaveForArg0)
asmgen.out(" pla")
}
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
asmgen.out(" pla")
if(needAsaveForArg0 && !asmgen.needAsaveForExpr(fcall.args[1])) {
// first 0 then 1
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
} else if(!needAsaveForArg0 && asmgen.needAsaveForExpr(fcall.args[1])) {
// first 1 then 0
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if (needAsaveForArg0)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if (needAsaveForArg0)
asmgen.out(" pla")
}
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
if (needAsaveForArg0)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
if (needAsaveForArg0)
asmgen.out(" pla")
asmgen.out(" tax")
}
@@ -1097,7 +1233,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
@@ -1108,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)
@@ -1125,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)
@@ -1197,7 +1337,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
@@ -1207,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)
@@ -1272,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)
@@ -1285,11 +1428,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.forDt(BaseDataType.FLOAT), value.position)
val addr = PtAddressOf(value.position)
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, 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.forDt(BaseDataType.FLOAT))
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT)
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
}
@@ -1306,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)
@@ -1316,7 +1459,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(conv.dt), null, value.position, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, value.position)
asmgen.translateNormalAssignment(assign, scope)
}
conv.reg != null -> {
@@ -1324,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)
@@ -1334,7 +1477,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, value.position, null, asmgen)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, value.position)
asmgen.translateNormalAssignment(assign, scope)
}
else -> throw AssemblyError("callconv")

View File

@@ -1,42 +0,0 @@
package prog8.codegen.cpu6502
import prog8.code.ast.IPtSubroutine
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSub
import prog8.code.core.DataType
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
when(this) {
is PtAsmSub -> {
return returns
}
is PtSub -> {
// for non-asm subroutines, determine the return registers based on the type of the return value
return if(returntype==null)
emptyList()
else {
val register = when {
returntype!!.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
returntype!!.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
returntype!!.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
listOf(Pair(register, returntype!!))
}
}
}
}
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
return when {
returntype?.isByteOrBool==true -> RegisterOrStatusflag(RegisterOrPair.A, null)
returntype?.isWord==true -> RegisterOrStatusflag(RegisterOrPair.AY, null)
returntype?.isFloat==true -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
returntype==null -> null
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
}
}

View File

@@ -33,224 +33,351 @@ internal class ForLoopsAsmGen(
}
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.add(endLabel)
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
if(range.step.asConstInteger()!! < -1) {
val limit = range.to.asConstInteger()
if(limit==0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
when {
iterableDt.isByteArray -> {
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
if (stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.removeLast()
}
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
when (stepsize) {
-1 if range.to.asConstInteger()==0 -> {
// simple loop downto 0 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
}
-1 if range.to.asConstInteger()==1 -> {
// simple loop downto 1 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
dec $varname
bne $loopLabel""")
}
1, -1 -> forOverBytesRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else -> forOverBytesRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
}
}
private fun forOverBytesRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
// bytes range, step 1 or -1
val stepsize = range.step.asConstInteger()!!
val incdec = if(stepsize==1) "inc" else "dec"
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.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) {
if(stepsize<0) {
asmgen.out("""
dec $varname
lda $varname
cmp #255
bne $loopLabel""")
clc
sbc $varname
bvc +
eor #$80
+ bpl $endLabel""")
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
else {
asmgen.out("""
dec $varname
bne $loopLabel""")
sec
sbc $varname
bvc +
eor #$80
+ bmi $endLabel""")
}
else if (stepsize==1 || stepsize==-1) {
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0) {
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
}
else
asmgen.out("""
sec
sbc $varname
bvc +
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0) {
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
}
else
asmgen.out(" cmp $varname | bcc $endLabel")
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
} else {
if(stepsize<0) {
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
asmgen.out("""
clc
sbc $varname
bvc +
eor #${'$'}80
+ bpl $endLabel""")
else
asmgen.out("""
sec
sbc $varname
bvc +
eor #${'$'}80
+ bmi $endLabel""")
} else {
if(stepsize<0)
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
cmp $varname
beq +
bcs $endLabel
+""")
else
asmgen.out(" cmp $varname | bcc $endLabel")
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
if(stepsize>0) {
asmgen.out("""
lda $varname
clc
adc #$stepsize
sta $varname
$modifiedLabel cmp #0 ; modified
bmi $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
lda $varname
sec
sbc #${stepsize.absoluteValue}
sta $varname
$modifiedLabel cmp #0 ; modified
bpl $loopLabel""")
}
asmgen.out(endLabel)
}
else {
asmgen.out(" cmp $varname | bcc $endLabel")
}
}
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
if(stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.out("""
lda $varname
cmp $toValueVar
beq $endLabel
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
// use self-modifying code to store the loop end comparison value
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0) {
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
clc
sbc $varname
bvc +
eor #$80
+ bpl $endLabel""")
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
else
asmgen.out("""
lda $varname
cmp #1
bne +
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
sec
sbc $varname
bvc +
eor #$80
+ bmi $endLabel""")
} else {
if(stepsize<0) {
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
}
else if (stepsize == 1 || stepsize == -1) {
// words, step 1 or -1
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
else {
asmgen.out(" cmp $varname | bcc $endLabel")
}
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
private fun forOverBytesRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
// bytes range, step >= 2 or <= -2
val stepsize = range.step.asConstInteger()!!
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
// pre-check for end already reached
if(iterableDt.isSignedByteArray) {
asmgen.out(" sta $modifiedLabel+1")
if(stepsize<0)
asmgen.out("""
clc
sbc $varname
bvc +
eor #$80
+ bpl $endLabel""")
else
asmgen.out("""
sec
sbc $varname
bvc +
eor #$80
+ bmi $endLabel""")
} else {
if(stepsize<0)
asmgen.out("""
cmp $varname
beq +
bcs $endLabel
+""")
else {
asmgen.out(" cmp $varname | bcc $endLabel")
}
asmgen.out(" sta $modifiedLabel+1")
}
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.romableError("self-modifying code (forloop over bytes range)", forloop.position) // TODO fix romable; there is self-modifying code below
if(stepsize>0) {
asmgen.out("""
lda $varname
clc
adc #$stepsize
sta $varname
$modifiedLabel cmp #0 ; modified
bmi $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
lda $varname
sec
sbc #${stepsize.absoluteValue}
sta $varname
$modifiedLabel cmp #0 ; modified
bpl $loopLabel""")
}
asmgen.out(endLabel)
}
private fun forOverNonconstWordRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
if(stepsize==-1 && range.to.asConstInteger()==0) {
// simple loop downto 0 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize==-1 && range.to.asConstInteger()==1) {
// simple loop downto 1 step -1 (words)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #1
bne +
lda $varname+1
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
else if (stepsize == 1 || stepsize == -1)
forOverWordsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else if (stepsize > 0)
forOverWordsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else
forOverWordsRangeStepGreaterOneDescending(range, varname, iterableDt, loopLabel, endLabel, stmt)
}
private fun forOverWordsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
// words range, step 1 or -1
val stepsize = range.step.asConstInteger()!!
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.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out(" sta $toValueVar")
asmgen.out(" sty $toValueVar+1")
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.out("""
lda $varname+1
cmp $toValueVar+1
bne +
lda $varname
cmp $toValueVar
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
} else {
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(forloop.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
else if (stepsize > 0) {
// (u)words, step >= 2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
}
private fun forOverWordsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, stmt: PtForLoop) {
// (u)words, step >= 2
val stepsize = range.step.asConstInteger()!!
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.translate(stmt.statements)
if (iterableDt.isUnsignedWordArray) {
asmgen.out("""
asmgen.romableError("self-modifying code (forloop over word range)", stmt.position) // TODO fix romable; there is self-modifying code below
if (iterableDt.isUnsignedWordArray) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -266,8 +393,8 @@ $modifiedLabel2 lda #0 ; modified
bcc $endLabel
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
@@ -283,20 +410,23 @@ $modifiedLabel lda #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else {
}
}
// (u)words, step <= -2
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
private fun forOverWordsRangeStepGreaterOneDescending(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, stmt: PtForLoop) {
// (u)words, step <= -2
val stepsize = range.step.asConstInteger()!!
val modifiedLabel = asmgen.makeLabel("for_modified")
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
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
@@ -313,12 +443,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.removeLast()
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@@ -330,23 +454,23 @@ $endLabel""")
sta P8ZP_SCRATCH_W2 ; to
sty P8ZP_SCRATCH_W2+1 ; to
lda $fromVar
cmp P8ZP_SCRATCH_W2
cmp P8ZP_SCRATCH_W2
lda $fromVar+1
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #${'$'}80
+ bmi $endLabel
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bmi $endLabel
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1""")
else
asmgen.out("""
sta P8ZP_SCRATCH_REG
cmp $fromVar
tya
sbc $fromVar+1
bvc +
eor #${'$'}80
+ bmi $endLabel
cmp $fromVar
tya
sbc $fromVar+1
bvc +
eor #$80
+ bmi $endLabel
lda P8ZP_SCRATCH_REG""")
} else {
if(stepsize<0)
@@ -362,11 +486,11 @@ $endLabel""")
+""")
else
asmgen.out("""
cpy $fromVar+1
bcc $endLabel
bne +
cmp $fromVar
bcc $endLabel
cpy $fromVar+1
bcc $endLabel
bne +
cmp $fromVar
bcc $endLabel
+""")
}
}
@@ -376,38 +500,56 @@ $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 -> {
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $loopLabel+1
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
inc $loopLabel+1
bne $loopLabel
inc $loopLabel+2
bne $loopLabel
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$endLabel""")
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$indexVar .byte 0
$endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = 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
@@ -422,20 +564,25 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(numElements>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.forDt(BaseDataType.UBYTE), null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
if(!asmgen.options.romable) {
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(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = 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
@@ -445,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
@@ -460,21 +607,25 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(numElements>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.forDt(BaseDataType.UBYTE), null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
if(!asmgen.options.romable) {
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(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val length = numElements * 2
val indexVar = 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
@@ -484,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
@@ -501,15 +652,17 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(indexVar, DataType.forDt(BaseDataType.UBYTE), null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
if(!asmgen.options.romable) {
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(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}

View File

@@ -1,5 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.StNodeType
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource
@@ -15,12 +16,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// just ignore any result values from the function call.
}
internal fun optimizeIntArgsViaCpuRegisters(params: List<PtSubroutineParameter>) =
when(params.size) {
1 -> params[0].type.isIntegerOrBool
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool
internal fun optimizeIntArgsViaCpuRegisters(params: List<PtSubroutineParameter>): Boolean {
// When the parameter(s) are passed via an explicit register or register pair,
// 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].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
}
}
internal fun translateFunctionCall(call: PtFunctionCall) {
// Output only the code to set up the parameters and perform the actual call
@@ -28,8 +33,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val symbol = asmgen.symbolTable.lookup(call.name)
val sub = symbol?.astNode as IPtSubroutine
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
return
}
val sub = symbol.astNode as IPtSubroutine
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub is PtAsmSub) {
@@ -47,6 +58,10 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(bank==null) {
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
if(varbank!=null) {
if(asmgen.options.romable)
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
// self-modifying code: set jsrfar bank argument
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
@@ -124,22 +139,24 @@ 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 (normalParams, registerParams) = sub.parameters.withIndex().partition { it.value.register == null }
val paramValues = parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) }
if (normalParams.isNotEmpty()) {
for (arg in normalParams.zip(call.args))
argumentViaVariable(sub, arg.first.value, arg.second)
for (p in normalParams)
argumentViaVariable(sub, p.first, p.second)
}
if (registerParams.isNotEmpty()) {
// the R0-R15 'registers' are not really registers. They're just special variables.
for (arg in registerParams.zip(call.args))
argumentViaVariable(sub, arg.first.value, arg.second)
for (p in registerParams)
argumentViaVariable(sub, p.first, p.second)
}
}
asmgen.out(" jsr $subAsmName")
@@ -150,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
@@ -159,12 +176,22 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
2 -> {
if(params[0].type.isByteOrBool && params[1].type.isByteOrBool) {
// 2 byte params, second in Y, first in A
argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A)
if(asmgen.needAsaveForExpr(args[1]))
asmgen.out(" pha")
argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y)
if(asmgen.needAsaveForExpr(args[1]))
asmgen.out(" pla")
if(asmgen.needAsaveForExpr(args[0]) && !asmgen.needAsaveForExpr(args[1])) {
// first 0 then 1
argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A)
argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y)
} else if(!asmgen.needAsaveForExpr(args[0]) && asmgen.needAsaveForExpr(args[1])) {
// first 1 then 0
argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y)
argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A)
} else {
argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A)
if (asmgen.needAsaveForExpr(args[1]))
asmgen.out(" pha")
argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y)
if (asmgen.needAsaveForExpr(args[1]))
asmgen.out(" pla")
}
} else {
throw AssemblyError("cannot use registers for word+byte")
}
@@ -208,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 {
@@ -300,20 +327,20 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
register!!
if(requiredDt.largerSizeThan(value.type)) {
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
} 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)
@@ -323,7 +350,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, target.position), scope)
asmgen.translateNormalAssignment(AsmAssignment(src, listOf(target), program.memsizer, target.position), scope)
}
return RegisterOrStatusflag(register, null)
}

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) {
@@ -32,10 +34,18 @@ internal class IfElseAsmGen(private val program: PtProgram,
val compareCond = stmt.condition as? PtBinaryExpression
if(compareCond!=null) {
val useBIT = asmgen.checkIfConditionCanUseBIT(compareCond)
if(useBIT!=null) {
// 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)
}
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")
}
@@ -55,9 +65,80 @@ 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}")
}
private fun translateIfBIT(ifElse: PtIfElse, jumpAfterIf: PtJump?, testForBitSet: Boolean, variable: PtIdentifier, bitmask: Int) {
// use a BIT instruction to test for bit 7 or 6 set/clear
fun branch(branchInstr: String, target: AsmGen6502Internal.JumpTarget) {
require(!target.needsExpressionEvaluation)
if(target.indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" $branchInstr ${target.asmLabel}")
}
when (bitmask) {
128 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
translateIfElseBodies("bpl", ifElse)
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
translateIfElseBodies("bmi", ifElse)
}
return
}
64 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
translateIfElseBodies("bvc", ifElse)
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
translateIfElseBodies("bvs", ifElse)
}
return
}
else -> throw AssemblyError("BIT only works for bits 6 and 7")
}
}
private fun checkNotExtsubReturnsStatusReg(condition: PtExpression) {
val fcall = condition as? PtFunctionCall
@@ -70,82 +151,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
val bittest = ifElse.condition as? PtBuiltinFunctionCall
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
val variable = bittest.args[0] as PtIdentifier
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
val testForBitSet = bittest.name.endsWith("_set")
when (bitnumber) {
7 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.needsExpressionEvaluation)
if(target.indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bmi ${target.asmLabel}")
}
else
translateIfElseBodies("bpl", ifElse)
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.needsExpressionEvaluation)
if(target.indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bpl ${target.asmLabel}")
}
else
translateIfElseBodies("bmi", ifElse)
}
return
}
6 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.needsExpressionEvaluation)
if(target.indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvs ${target.asmLabel}")
}
else
translateIfElseBodies("bvc", ifElse)
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.needsExpressionEvaluation)
if(target.indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvc ${target.asmLabel}")
}
else
translateIfElseBodies("bvs", ifElse)
}
return
}
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
}
}
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
asmgen.assignConditionValueToRegisterAndTest(ifElse.condition)
if(jumpAfterIf!=null)
@@ -154,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")
@@ -162,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 {
@@ -179,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}")
@@ -218,40 +311,11 @@ 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.forDt(BaseDataType.BOOL), stmt.definingISub(), condition.position, register=RegisterOrPair.A)
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
if (jumpAfterIf != null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
@@ -294,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})
+""")
@@ -314,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 {
@@ -361,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})
+""")
@@ -383,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 {
@@ -410,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
@@ -424,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})
+""")
@@ -461,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 {
@@ -474,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()
@@ -543,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})
+""")
@@ -570,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 {
@@ -598,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})
+""")
@@ -623,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 {
@@ -675,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})
+""")
@@ -702,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 {
@@ -729,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})
+""")
@@ -752,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 {
@@ -792,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")
@@ -808,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")
@@ -837,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})
+""")
@@ -865,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 {
@@ -886,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")
@@ -916,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})
+""")
@@ -944,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 {
@@ -981,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})
+""")
@@ -1009,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 {
@@ -1031,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")
@@ -1061,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})
+""")
@@ -1089,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 {
@@ -1144,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")
}
@@ -1165,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")
}
@@ -1204,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})
+""")
@@ -1229,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 {
@@ -1256,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})
+""")
@@ -1281,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 {
@@ -1310,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})
+""")
@@ -1339,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 {
@@ -1369,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})
+""")
@@ -1398,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 {
@@ -1431,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})
+""")
@@ -1460,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 {
@@ -1489,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})
+""")
@@ -1518,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 {
@@ -1540,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")
@@ -1604,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")
@@ -1656,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

@@ -1,19 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtBuiltinFunctionCall
import prog8.code.ast.PtExpression
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtIfExpression
import prog8.code.ast.PtNumber
import prog8.code.ast.PtPrefix
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.CpuRegister
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.LogicalOperators
import prog8.code.core.RegisterOrPair
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.TargetStorageKind
@@ -21,24 +9,24 @@ import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, private val assignmentAsmGen: AssignmentAsmGen, private val errors: IErrorReporter) {
internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) {
require(target.datatype==expr.type)
require(target.datatype==expr.type || target.datatype.isUnsignedWord && expr.type.isString)
val falseLabel = asmgen.makeLabel("ifexpr_false")
val endLabel = asmgen.makeLabel("ifexpr_end")
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 -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false)
expr.type.isWord || expr.type.isString -> {
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)
}
@@ -55,46 +43,26 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
private fun evalIfExpressionConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) {
if (condition is PtBinaryExpression) {
val rightDt = condition.right.type
return when {
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
}
else if(condition is PtPrefix && condition.operator=="not") {
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
} else {
// 'simple' condition, check if it is a byte bittest
val bittest = condition as? PtBuiltinFunctionCall
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
val variable = bittest.args[0] as PtIdentifier
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
val testForBitSet = bittest.name.endsWith("_set")
when (bitnumber) {
7 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bpl $falseLabel")
else asmgen.out(" bmi $falseLabel")
return
}
6 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bvc $falseLabel")
else asmgen.out(" bvs $falseLabel")
return
}
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
when (condition) {
is PtBinaryExpression -> {
val rightDt = condition.right.type
return when {
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
}
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
asmgen.assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
is PtPrefix if condition.operator=="not" -> {
throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values")
}
else -> {
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
asmgen.assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
}
}
@@ -119,7 +87,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" beq $falseLabel")
}
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.forDt(BaseDataType.BOOL), condition.definingISub(), condition.position, register=RegisterOrPair.A)
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, condition.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
asmgen.out(" beq $falseLabel")
} else {
@@ -227,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 +
@@ -251,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
@@ -274,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 +
@@ -297,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
@@ -316,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")
}
}
@@ -331,13 +299,37 @@ 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")
}
}
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
// optimized code for byte comparisons with 0
val useBIT = asmgen.checkIfConditionCanUseBIT(condition)
if(useBIT!=null) {
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testForBitSet, variable, bitmask) = useBIT
when (bitmask) {
128 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bpl $falseLabel")
else asmgen.out(" bmi $falseLabel")
return
}
64 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) asmgen.out(" bvc $falseLabel")
else asmgen.out(" bvs $falseLabel")
return
}
else -> throw AssemblyError("BIT can only work on bits 7 and 6")
}
}
asmgen.assignConditionValueToRegisterAndTest(condition.left)
when (condition.operator) {
"==" -> asmgen.out(" bne $falseLabel")

View File

@@ -39,22 +39,22 @@ internal class ProgramAndVarsGen(
// the global list of all floating point constants for the whole program
asmgen.out("; global float constants")
for (flt in allocator.globalFloatConsts) {
val floatFill = compTarget.machine.getFloatAsmBytes(flt.key)
val floatFill = compTarget.getFloatAsmBytes(flt.key)
val floatvalue = flt.key
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
structInstances2asm()
memorySlabs()
tempVars()
footer()
}
}
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {
val cpu = when(compTarget.cpu) {
CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "w65c02"
CpuType.CPU65C02 -> "w65c02"
else -> "unsupported"
}
@@ -87,209 +87,224 @@ internal class ProgramAndVarsGen(
}
}
when(options.output) {
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
if(options.compTarget.customLauncher.isNotEmpty()) {
asmgen.out("; ---- custom launcher assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
for(line in options.compTarget.customLauncher) {
asmgen.out(line)
}
OutputType.PRG -> {
when(options.launcher) {
CbmPrgLauncherType.BASIC -> {
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
return
}
if(options.output == OutputType.LIBRARY) {
asmgen.out("; ---- library assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" jmp p8b_main.p8s_start")
// note: the jmp above has 2 effects:
// 1. it prevents 64tass from stripping away all procs as unused code
// 2. it functions as the first entrypoint of the library, required anyway, to run the variable initialization/bss clear bootstrap code.
} else {
when (options.output) {
OutputType.LIBRARY -> { }
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if (!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
OutputType.PRG -> {
when (options.launcher) {
CbmPrgLauncherType.BASIC -> {
if (options.loadAddress != options.compTarget.PROGRAM_LOAD_ADDRESS) {
errors.err(
"BASIC output must have load address ${options.compTarget.PROGRAM_LOAD_ADDRESS.toHex()}",
program.position
)
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if (!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
// this is the same as RAW
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if (!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
// this is the same as RAW
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if (!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
asmgen.out("prog8_program_start\t; start of program label")
asmgen.out(" cld")
asmgen.out(" tsx ; save stackpointer for sys.exit()")
asmgen.out(" stx prog8_lib.orig_stackpointer")
if(!options.noSysInit)
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
}
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
asmgen.out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>sys.reset_system
pha
lda #<sys.reset_system
pha""")
}
if (options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
asmgen.out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>sys.reset_system
pha
lda #<sys.reset_system
pha""")
}
when(compTarget.name) {
"cx16" -> {
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c128" -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
else -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
when (compTarget.name) {
"cx16" -> {
if (options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
"c128" -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
else -> {
asmgen.out(" jsr p8b_main.p8s_start")
asmgen.out(" jmp p8_sys_startup.cleanup_at_exit")
}
}
}
}
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.machine.FLOAT_MEM_SIZE}")
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
var relocatedBssEnd = 0u
if(options.varsGolden) {
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
options.compTarget.BSSGOLDENRAM_END == 0u ||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssVars = true
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
}
else if(options.varsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
if(options.compTarget.BSSHIGHRAM_START == 0u ||
options.compTarget.BSSHIGHRAM_END == 0u ||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssVars = true
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
}
if(options.slabsGolden) {
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
options.compTarget.BSSGOLDENRAM_END == 0u ||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssSlabs = true
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
}
else if(options.slabsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
if(options.compTarget.BSSHIGHRAM_START == 0u ||
options.compTarget.BSSHIGHRAM_END == 0u ||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssSlabs = true
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
}
asmgen.out("; bss sections")
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 {
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
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\"")
}
}
if(relocatedBssEnd >= options.memtopAddress)
options.memtopAddress = relocatedBssEnd+1u
asmgen.out(" ; memtop check")
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
}
@@ -316,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")
}
@@ -335,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
@@ -349,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.
@@ -403,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
@@ -421,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)")
@@ -457,27 +555,27 @@ 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.FLOAT -> asmgen.out("$name .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
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")
}
}
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
asmgen.out(" .send BSS")
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
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)
@@ -532,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("+")
@@ -601,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,
@@ -624,6 +737,7 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
}
alignedStrings.sortedBy { it.align }.forEach {
outputStringvar(
@@ -632,13 +746,22 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
}
notAlignedOther.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
}
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
}
}
}
@@ -650,57 +773,67 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> asmgen.out("${variable.name}\t.char ?")
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ?")
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
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()}")
}
private fun staticVariable2asm(variable: StStaticVariable) {
val initialValue: Number =
if(variable.initializationNumericValue!=null) {
if(variable.dt.isFloat)
variable.initializationNumericValue!!
else
variable.initializationNumericValue!!.toInt()
} else 0
if(!variable.dt.isArray && !variable.dt.isString) {
throw AssemblyError("static variables with an initialization value can only be an array or a string, not ${variable.dt} (${variable.name} ${variable.astNode?.position}")
// because numeric variables are in the BSS section and get initialized via assignment statements
}
// val initialValue: Number =
// if(variable.initializationNumericValue!=null) {
// if(variable.dt.isFloat)
// variable.initializationNumericValue!!
// else
// variable.initializationNumericValue!!.toInt()
// } else 0
//
val dt=variable.dt
when {
dt.isBool || dt.isUnsignedByte -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
dt.isFloat -> {
if(initialValue==0) {
asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
} else {
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
}
}
// dt.isBool || dt.isUnsignedByte -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
// dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
// dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
// dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
// dt.isFloat -> {
// if(initialValue==0) {
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
// } else {
// val floatFill = compTarget.getFloatAsmBytes(initialValue)
// asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
// }
// }
dt.isString -> {
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")
@@ -708,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 -> {
@@ -767,7 +900,7 @@ internal class ProgramAndVarsGen(
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
compTarget.machine.getFloatAsmBytes(it.number!!)
compTarget.getFloatAsmBytes(it.number!!)
}
asmgen.out(varname)
for (f in array.zip(floatFills))
@@ -810,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())
@@ -875,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

@@ -2,10 +2,7 @@ package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onSuccess
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.*
import prog8.code.core.*
@@ -14,7 +11,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private val errors: IErrorReporter
) {
private val zeropage = options.compTarget.machine.zeropage
private val zeropage = options.compTarget.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
@@ -23,7 +20,13 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
zeropageVars = zeropage.allocatedVariables
}
internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
internal fun isZpVar(scopedName: String): Boolean {
if(scopedName in zeropageVars)
return true
val v = symboltable.lookup(scopedName)
return if(v is StMemVar) v.address <= 255u else false
}
internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number]
@@ -49,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
@@ -57,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
)
@@ -76,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
)
@@ -89,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

@@ -2,7 +2,10 @@ package prog8.codegen.cpu6502.assignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtExpression
import prog8.code.core.*
import prog8.code.core.AssemblyError
import prog8.code.core.ComparisonOperators
import prog8.code.core.DataType
import prog8.code.core.RegisterOrPair
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.AsmGen6502Internal
@@ -17,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)
@@ -43,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")
}
}
@@ -50,17 +61,17 @@ 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.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1")
asmgen.assignRegister(RegisterOrPair.A, assign.target)
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.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1")
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
@@ -73,25 +84,25 @@ 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.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | and P8ZP_SCRATCH_B1")
asmgen.assignRegister(RegisterOrPair.A, assign.target)
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.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | ora P8ZP_SCRATCH_B1")
asmgen.assignRegister(RegisterOrPair.A, assign.target)
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.forDt(BaseDataType.UBYTE))
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | eor P8ZP_SCRATCH_B1")
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
@@ -190,14 +201,14 @@ internal class AnyExprAsmGen(
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
if (!right.isSimple()) asmgen.popFAC1()
}
else -> TODO("don't know how to evaluate float expression for selected compilation target")
else -> TODO("don't know how to evaluate float expression for selected compilation target ${left.position}")
}
}
private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToVariable(expr.right, "floats.floats_temp_var", DataType.forDt(BaseDataType.FLOAT))
asmgen.assignExpressionToVariable(expr.right, "floats.floats_temp_var", DataType.FLOAT)
if(!expr.right.isSimple()) asmgen.popFAC1()
asmgen.out(" lda #<floats.floats_temp_var | ldy #>floats.floats_temp_var")
}

View File

@@ -3,14 +3,15 @@ package prog8.codegen.cpu6502.assignment
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.returnsWhatWhere
internal enum class TargetStorageKind {
VARIABLE,
VARIABLE, // non-pointer variable
ARRAY,
MEMORY,
REGISTER
REGISTER,
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,16 +41,36 @@ 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 {
fun fromAstAssignmentMulti(targets: List<PtAssignTarget>, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): List<AsmAssignTarget> {
return targets.map {
if(it.void)
AsmAssignTarget(TargetStorageKind.VOID, asmgen, DataType.UNDEFINED, null, it.position)
else
fromAstAssignment(it, definingSub, asmgen)
}
}
fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
with(target) {
when {
@@ -68,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")
}
}
@@ -78,18 +101,18 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
val dt = DataType.forDt(if(signed) BaseDataType.BYTE else BaseDataType.UBYTE)
val dt = if(signed) DataType.BYTE else DataType.UBYTE
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> {
val dt = DataType.forDt(if(signed) BaseDataType.WORD else BaseDataType.UWORD)
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> {
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.forDt(BaseDataType.FLOAT), scope, pos, register = registers)
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
}
RegisterOrPair.R0,
RegisterOrPair.R1,
@@ -107,7 +130,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
val dt = DataType.forDt(if(signed) BaseDataType.WORD else BaseDataType.UWORD)
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
}
@@ -125,14 +148,16 @@ 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.REGISTER -> {
false
TargetStorageKind.POINTER -> {
TODO("is pointer deref target same as expression? ${this.position}")
}
TargetStorageKind.REGISTER -> false
TargetStorageKind.VOID -> false
}
}
@@ -152,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 {
@@ -162,7 +190,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
val bv = value as? PtBool
if(bv!=null)
return AsmAssignSource(SourceStorageKind.LITERALBOOLEAN, program, asmgen, DataType.forDt(BaseDataType.BOOL), boolean = bv)
return AsmAssignSource(SourceStorageKind.LITERALBOOLEAN, program, asmgen, DataType.BOOL, boolean = bv)
return when(value) {
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
@@ -183,7 +211,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
}
is PtMemoryByte -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.forDt(BaseDataType.UBYTE), memory = value)
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is PtArrayIndexer -> {
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
@@ -194,9 +222,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
val returnType =
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
else -> {
@@ -226,27 +257,30 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
val target: AsmAssignTarget,
val targets: List<AsmAssignTarget>,
val memsizer: IMemSizer,
val position: Position) {
init {
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(!source.datatype.isUndefined) { "must not be placeholder/undefined datatype at $position" }
if(!source.datatype.isArray && !target.datatype.isArray)
require(memsizer.memorySize(source.datatype, null) <= memsizer.memorySize(target.datatype, null)) {
"source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
}
targets.forEach { target ->
if (!source.datatype.isArray && !source.datatype.isUndefined && !target.datatype.isArray && !target.datatype.isUndefined)
require(memsizer.memorySize(source.datatype, null) <= memsizer.memorySize(target.datatype, null)) {
"source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
}
}
}
val target: AsmAssignTarget
get() = targets.single()
}
internal class AsmAssignment(source: AsmAssignSource,
target: AsmAssignTarget,
targets: List<AsmAssignTarget>,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)
position: Position): AsmAssignmentBase(source, targets, memsizer, position)
internal class AsmAugmentedAssignment(source: AsmAssignSource,
val operator: String,
target: AsmAssignTarget,
memsizer: IMemSizer,
position: Position): AsmAssignmentBase(source, target, memsizer, position)
position: Position): AsmAssignmentBase(source, listOf(target), memsizer, position)

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,6 +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 printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@@ -5,12 +5,15 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.shouldBe
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.source.SourceCode
import prog8.code.target.C64Target
import prog8.codegen.cpu6502.AsmGen6502
import prog8.codegen.cpu6502.VariableAllocator
import java.nio.file.Files
import kotlin.io.path.Path
@@ -26,8 +29,9 @@ class TestCodegen: FunSpec({
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
}
@@ -48,13 +52,14 @@ class TestCodegen: FunSpec({
val codegen = AsmGen6502(prefixSymbols = false, 0)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"pi",
DataType.forDt(BaseDataType.UBYTE),
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
PtNumber(BaseDataType.UBYTE, 0.0, Position.DUMMY),
false,
null,
null,
Position.DUMMY
))
@@ -63,6 +68,7 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -72,29 +78,31 @@ class TestCodegen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"xx",
DataType.forDt(BaseDataType.WORD),
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
PtNumber(BaseDataType.WORD, 1.0, Position.DUMMY),
false,
null,
null,
Position.DUMMY
))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.forDt(BaseDataType.UBYTE), Position.DUMMY).also { idx ->
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
idx.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.forDt(BaseDataType.UBYTE), Position.DUMMY)
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
value.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
@@ -103,15 +111,15 @@ class TestCodegen: FunSpec({
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(BaseDataType.WORD, 42.0, Position.DUMMY))
@@ -119,10 +127,10 @@ class TestCodegen: FunSpec({
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.forDt(BaseDataType.UWORD), Position.DUMMY))
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
@@ -130,7 +138,7 @@ class TestCodegen: FunSpec({
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.forDt(BaseDataType.UWORD), 100u, null, Position.DUMMY))
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
@@ -159,5 +167,15 @@ class TestCodegen: FunSpec({
}
}
}
test("memory mapped zp var is correctly considered to be zp var") {
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val st = SymbolTable(program)
st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null))
st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null))
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests())
allocator.isZpVar("zpmemvar") shouldBe true
allocator.isZpVar("normalmemvar") shouldBe false
}
})

View File

@@ -1,16 +1,15 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
implementation(project(":codeGenIntermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
}
sourceSets {
@@ -24,4 +23,4 @@ sourceSets {
}
}
// note: there are no unit tests in this module!
// note: there are no unit tests in this module!

View File

@@ -13,5 +13,6 @@
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
</component>
</module>

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.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
@@ -21,11 +21,13 @@ class ExperiCodeGen: ICodeGeneratorBackend {
// but you can also use the Intermediate Representation to build a codegen on:
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
// 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
}
}
@@ -33,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")
}
@@ -7,15 +5,14 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0")
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 {
@@ -45,4 +42,4 @@ tasks.test {
testLogging {
events("skipped", "failed")
}
}
}

View File

@@ -11,10 +11,10 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<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)
@@ -42,11 +47,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"ror" -> funcRolRor(call)
"rol2" -> funcRolRor(call)
"ror2" -> funcRolRor(call)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse/ifExpression statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse/ifExpression statement")
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -56,12 +61,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val valueTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
return if(resultType==IRDataType.FLOAT) {
val resultFpReg = codeGen.registers.nextFreeFloat()
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = resultFpReg, fpReg2 = valueTr.resultFpReg), null)
ExpressionCodeResult(result, resultType, -1, resultFpReg)
}
else {
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(resultType)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
ExpressionCodeResult(result, resultType, resultReg, -1)
}
@@ -75,12 +80,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.next(IRDataType.WORD), -1) // TODO actually the result is returned in CPU registers AY...
}
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])
@@ -93,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])
@@ -125,7 +128,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.DIVMOD, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
divisionReg = tr.resultReg
remainderReg = codeGen.registers.nextFree()
remainderReg = codeGen.registers.next(type)
} else {
val numTr = exprGen.translateExpression(number)
addToResult(result, numTr, numTr.resultReg, -1)
@@ -145,13 +148,13 @@ 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)
addToResult(result, right, right.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to left.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, left.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcCmp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -178,7 +181,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
when (sourceDt.base) {
BaseDataType.BYTE -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.nextFree()
val compareReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
@@ -189,7 +192,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
BaseDataType.WORD -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.nextFree()
val compareReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
@@ -199,7 +202,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.nextFreeFloat()
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
@@ -210,7 +213,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(tr.dt)
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
@@ -233,7 +236,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
when(dt.base) {
BaseDataType.UBYTE -> {
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg)
}
@@ -241,15 +244,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
BaseDataType.UWORD -> {
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
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)
val resultFpReg = codeGen.registers.nextFreeFloat()
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg)
}
@@ -261,7 +264,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(IRDataType.WORD)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
@@ -279,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])
@@ -406,7 +408,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
return if(dt==IRDataType.FLOAT) {
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
@@ -415,7 +417,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.nextFreeFloat()
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
}
@@ -423,7 +425,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val resultRegister = codeGen.registers.next(dt)
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
@@ -432,7 +434,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(dt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
}
@@ -474,7 +476,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return if(address is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
val addressNum = address.number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
@@ -484,7 +486,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val addressTr = exprGen.translateExpression(address)
addToResult(result, addressTr, addressTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
}
@@ -497,22 +499,34 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return ExpressionCodeResult(code, IRDataType.BYTE, resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
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 {
return exprGen.translateExpression(call.args.single())
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
// ....To be more strict, maybe we should introduce a new result register that is of type .b?
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
@@ -545,10 +559,27 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
if(arr.splitWords) TODO("IR rol/ror on split words array")
val variable = arr.variable.name
val offset = codeGen.program.memsizer.memorySize(arr.type, index)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null)
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) {
Opcode.ROXRM, Opcode.RORM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
}
Opcode.ROXLM, Opcode.ROLM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
}
else -> throw AssemblyError("wrong rol/ror opcode")
}
}
} else {
val offset = codeGen.program.memsizer.memorySize(arr.type, index)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null)
}
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -577,7 +608,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
is PtIdentifier -> {
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
@@ -587,7 +618,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
@@ -599,10 +630,12 @@ 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.nextFree()
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
@@ -618,7 +651,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
@@ -633,46 +666,48 @@ 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) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
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 {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
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

@@ -25,7 +25,7 @@ class IRCodeGen(
verifyNameScoping(program, symbolTable)
changeGlobalVarInits(symbolTable)
val irSymbolTable = IRSymbolTable.fromAstSymboltable(symbolTable)
val irSymbolTable = convertStToIRSt(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
// collect global variables initializers
@@ -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
)
}
}
@@ -231,7 +241,11 @@ class IRCodeGen(
chunk += IRInstruction(Opcode.BREAKPOINT)
listOf(chunk)
}
is PtAlign -> TODO("ir support for inline %align")
is PtAlign -> {
val chunk = IRCodeChunk(null, null)
chunk += IRInstruction(Opcode.ALIGN, immediate = node.align.toInt())
listOf(chunk)
}
is PtConditionalBranch -> translate(node)
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
@@ -252,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")
}
@@ -280,31 +296,23 @@ class IRCodeGen(
val address = goto.target.asConstInteger()
val label = (goto.target as? PtIdentifier)?.name
if(address!=null) {
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, address = address)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, address = address)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, address = address)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, address = address)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, address = address)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, address = address)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, address = address)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, address = address)
}
val branchIns = IRBranchInstr(branch.condition, address=address)
addInstr(result, branchIns, null)
} else if(label!=null && !isIndirectJump(goto)) {
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
}
val branchIns = IRBranchInstr(branch.condition, label = label)
addInstr(result, branchIns, null)
} else {
TODO("JUMP to expression address ${goto.target}") // keep in mind the branch insruction that may need to precede this!
val skipJumpLabel = createLabelName()
// note that the branch opcode used is the opposite as the branch condition, because it needs to skip the indirect jump
val branchIns = IRInvertedBranchInstr(branch.condition, label = skipJumpLabel)
// evaluate jump address expression into a register and jump indirectly to it
addInstr(result, branchIns, null)
val tr = expressionEval.translateExpression(goto.target)
result += tr.chunks
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.JUMPI, reg1=tr.resultReg)
}
result += IRCodeChunk(skipJumpLabel, null)
}
if(branch.falseScope.children.isNotEmpty())
result += translateNode(branch.falseScope)
@@ -313,16 +321,7 @@ class IRCodeGen(
val elseLabel = createLabelName()
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
BranchCondition.CC -> IRInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, labelSymbol = elseLabel)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
}
val branchIns = IRInvertedBranchInstr(branch.condition, label = elseLabel)
addInstr(result, branchIns, null)
result += translateNode(branch.trueScope)
if(branch.falseScope.children.isNotEmpty()) {
@@ -337,6 +336,60 @@ class IRCodeGen(
return result
}
private fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
if(label!=null)
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
}
else if(address!=null) {
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, address = address)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, address = address)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, address = address)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, address = address)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, address = address)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, address = address)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, address = address)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, address = address)
}
}
else throw AssemblyError("need label or address for branch")
}
private fun IRInvertedBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
if(label!=null)
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
}
else if(address!=null) {
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCC, address = address)
BranchCondition.CC -> IRInstruction(Opcode.BSTCS, address = address)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, address = address)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, address = address)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, address = address)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, address = address)
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, address = address)
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, address = address)
}
}
else throw AssemblyError("need label or address for branch")
}
private fun labelFirstChunk(chunks: IRCodeChunks, label: String): IRCodeChunks {
if(chunks.isEmpty()) {
return listOf(
@@ -379,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)
}
@@ -427,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.nextFree()
val tmpReg = registers.nextFree()
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@@ -439,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)
@@ -450,20 +513,19 @@ 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 {
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
val tmpRegLsb = registers.next(IRDataType.BYTE)
val tmpRegMsb = registers.next(IRDataType.BYTE)
val concatReg = registers.next(IRDataType.WORD)
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 {
@@ -507,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
@@ -593,9 +655,8 @@ 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 indexReg = registers.nextFree()
val loopvarDt = when(loopvar) {
is StMemVar -> loopvar.dt
is StStaticVariable -> loopvar.dt
@@ -611,6 +672,7 @@ class IRCodeGen(
val rangeEndExclusiveWrapped = if(loopvarDtIr==IRDataType.BYTE) rangeEndExclusiveUntyped and 255 else rangeEndExclusiveUntyped and 65535
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
val indexReg = registers.next(loopvarDtIr)
chunk += IRInstruction(Opcode.LOAD, loopvarDtIr, reg1=indexReg, immediate = iterable.first)
chunk += IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol)
result += chunk
@@ -703,7 +765,7 @@ class IRCodeGen(
}
}
else -> {
val valueReg = registers.nextFree()
val valueReg = registers.next(dt)
if(value>0) {
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value)
code += if(knownAddress!=null)
@@ -730,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
}
@@ -745,41 +807,43 @@ class IRCodeGen(
else
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, labelSymbol = symbol)
} else {
val factorReg = registers.nextFreeFloat()
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.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
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
@@ -793,7 +857,7 @@ class IRCodeGen(
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.nextFree()
val pow2reg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, address = knownAddress)
@@ -807,12 +871,13 @@ class IRCodeGen(
IRInstruction(Opcode.STOREZM, dt, labelSymbol = symbol)
}
else {
val factorReg = registers.nextFree()
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
@@ -835,14 +900,14 @@ class IRCodeGen(
if(factor==1.0)
return code
if(factor==0.0) {
val maxvalueReg = registers.nextFreeFloat()
val maxvalueReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Double.MAX_VALUE)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, address = knownAddress)
else
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, labelSymbol = symbol)
} else {
val factorReg = registers.nextFreeFloat()
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
@@ -864,8 +929,8 @@ class IRCodeGen(
code += IRInstruction(Opcode.ASR, dt, reg1=reg)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
}
} else {
@@ -874,8 +939,8 @@ class IRCodeGen(
code += IRInstruction(Opcode.LSR, dt, reg1=reg)
} else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSRN, dt, reg1 = reg, reg2 = pow2reg)
}
}
@@ -910,7 +975,7 @@ class IRCodeGen(
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
val pow2reg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
code += if (knownAddress != null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
@@ -927,7 +992,7 @@ class IRCodeGen(
}
else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
val pow2reg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
@@ -941,7 +1006,7 @@ class IRCodeGen(
{
// regular div
if (factor == 0) {
val reg = registers.nextFree()
val reg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, dt, reg1=reg, address = knownAddress)
@@ -949,7 +1014,7 @@ class IRCodeGen(
IRInstruction(Opcode.STOREM, dt, reg1=reg, labelSymbol = symbol)
}
else {
val factorReg = registers.nextFree()
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
code += if(signed) {
if(knownAddress!=null)
@@ -986,6 +1051,7 @@ class IRCodeGen(
ifWithOnlyNormalJump_IntegerCond(ifElse, goto)
}
// floating-point condition only from here!
// we assume only a binary expression can contain a floating point.
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = expressionEval.translateExpression(condition.left)
@@ -994,7 +1060,7 @@ class IRCodeGen(
addToResult(result, rightTr, -1, rightTr.resultFpReg)
var afterIfLabel = ""
result += IRCodeChunk(null, null).also {
val compResultReg = registers.nextFree()
val compResultReg = registers.next(IRDataType.BYTE)
it += IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
@@ -1028,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)
@@ -1056,7 +1122,7 @@ class IRCodeGen(
else if(goto.target is PtIdentifier && !isIndirectJump(goto))
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = (goto.target as PtIdentifier).name)
else
TODO("JUMP to expression address ${goto.target}") // keep in mind the branch insruction that may need to precede this!
throw AssemblyError("non-indirect jump shouldn't have an expression as target")
}
}
}
@@ -1075,7 +1141,7 @@ class IRCodeGen(
if(identifier!=null && !isIndirectJump(goto))
IRInstruction(branchOpcode, labelSymbol = identifier.name)
else
TODO("JUMP to expression address ${goto.target}") // keep in mind the branch insruction that may need to precede this!
TODO("JUMP to expression address ${goto.target}")
}
}
@@ -1124,7 +1190,6 @@ class IRCodeGen(
}
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
@@ -1254,7 +1319,7 @@ class IRCodeGen(
else if(goto.target is PtIdentifier && !isIndirectJump(goto))
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, labelSymbol = (goto.target as PtIdentifier).name), null)
else
TODO("JUMP to expression address ${goto.target}") // keep in mind the branch insruction that may need to precede this!
throw AssemblyError("non-indirect jump shouldn't have an expression as target")
}
}
} else {
@@ -1313,7 +1378,7 @@ class IRCodeGen(
else if(goto.target is PtIdentifier && !isIndirectJump(goto))
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, labelSymbol = (goto.target as PtIdentifier).name), null)
else
TODO("JUMP to expression address ${goto.target}") // keep in mind the branch insruction that may need to precede this!
throw AssemblyError("non-indirect jump shouldn't have an expression as target")
}
}
}
@@ -1357,7 +1422,7 @@ class IRCodeGen(
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val compResultReg = registers.nextFree()
val compResultReg = registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
@@ -1412,10 +1477,6 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) {
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
val tr = expressionEval.translateExpression(condition)
if(addCmpiZero)
tr.chunks.last().instructions.add(IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0))
@@ -1440,6 +1501,41 @@ class IRCodeGen(
if(condition.operator in LogicalOperators)
return translateSimple(condition, Opcode.BSTEQ, false)
val useBIT = expressionEval.checkIfConditionCanUseBIT(condition)
if(useBIT!=null) {
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
addInstr(result, IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name), null)
val bitBranchOpcode = when(testBitSet) {
true -> when(bitmask) {
64 -> Opcode.BSTVC
128 -> Opcode.BSTPOS
else -> throw AssemblyError("need bit 6 or 7")
}
false -> when(bitmask) {
64 -> Opcode.BSTVS
128 -> Opcode.BSTNEG
else -> throw AssemblyError("need bit 6 or 7")
}
}
if(ifElse.hasElse()) {
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(bitBranchOpcode, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
result += IRCodeChunk(afterIfLabel, null)
} else {
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(bitBranchOpcode, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
return
}
val signed = condition.left.type.isSigned
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
@@ -1581,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 -> {
@@ -1597,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
@@ -1618,7 +1717,7 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
if(constRepeats==65536) {
// make use of the word wrap around to count to 65536
val resultRegister = registers.nextFree()
val resultRegister = registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, immediate = 0), null)
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
@@ -1671,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
}
@@ -1686,10 +1783,46 @@ class IRCodeGen(
private fun translate(ret: PtReturn): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val value = ret.value
if(ret.children.size>1) {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
// a floating point value is passed via FAC (just one fp value is possible)
val returnRegs = ret.definingISub()!!.returnsWhatWhere()
val values = ret.children.zip(returnRegs)
// first all but the first return values
for ((value, register) in values.drop(1)) {
val tr = expressionEval.translateExpression(value as PtExpression)
if(register.second.isFloat) {
addToResult(result, tr, -1, tr.resultFpReg)
result += setCpuRegister(register.first, IRDataType.FLOAT, -1, tr.resultFpReg)
}
else {
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
}
// finally do the first of the return values (this avoids clobbering of its value in AY)
values.first().also { (value, register) ->
val tr = expressionEval.translateExpression(value as PtExpression)
if(register.second.isFloat) {
addToResult(result, tr, -1, tr.resultFpReg)
result += setCpuRegister(register.first, IRDataType.FLOAT, -1, tr.resultFpReg)
}
else {
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
}
addInstr(result, IRInstruction(Opcode.RETURN), null)
return result
}
val value = ret.children.singleOrNull()
if(value==null) {
addInstr(result, IRInstruction(Opcode.RETURN), null)
} else {
value as PtExpression
if(value.type.isFloat) {
if(value is PtNumber) {
addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null)
@@ -1728,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.returntype, 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 }
}
@@ -1769,21 +1902,30 @@ class IRCodeGen(
is PtLabel -> {
irBlock += IRCodeChunk(child.name, null)
}
is PtJmpTable -> {
irBlock += IRCodeChunk(null, null).also {
for(addr in child.children) {
addr as PtIdentifier
it += IRInstruction(Opcode.JUMP, labelSymbol = addr.name)
}
}
}
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" }
@@ -1798,7 +1940,7 @@ class IRCodeGen(
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
return "${PtLabel.GENERATED_LABEL_PREFIX}$labelSequenceNumber"
return "${GENERATED_LABEL_PREFIX}$labelSequenceNumber"
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
@@ -1808,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))
@@ -1818,4 +1960,101 @@ class IRCodeGen(
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
}
}
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)
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=resultReg)
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=resultReg)
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=resultReg)
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = resultFpReg)
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = resultFpReg)
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
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!!
}
}
}
}
}
@@ -199,6 +193,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
IRInlineAsmChunk(label, candidate.assembly, candidate.isIR, candidate.next)
else
candidate
} else {
chunks += candidate
}
}
is IRInlineBinaryChunk -> {
@@ -210,6 +206,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
IRInlineBinaryChunk(label, candidate.data, candidate.next)
else
candidate
} else {
chunks += candidate
}
}
}
@@ -351,39 +349,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
}
// a SNZ etc. whose target register is not used can be removed altogether
if(ins.opcode in OpcodesThatSetRegFromStatusbits) {
val usages = regUsages(ins.reg1!!)
if(usages.toList().sumOf { it.second } <= 1) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
return changed
}
private fun regUsages(register: Int): Map<IRCodeChunkBase, Int> {
val chunks = mutableMapOf<IRCodeChunkBase, Int>()
irprog.foreachSub { sub ->
sub.chunks.forEach { chunk ->
val used = chunk.usedRegisters()
val numUsages = used.readRegs.getOrDefault(register, 0) + used.writeRegs.getOrDefault(register, 0)
if(numUsages>0) {
chunks[chunk] = numUsages
}
}
}
return chunks
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
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
@@ -403,15 +378,19 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
Opcode.AND -> {
if (ins.immediate == 0) {
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
changed = true
} else if (ins.immediate == 255 && ins.type == IRDataType.BYTE) {
chunk.instructions.removeAt(idx)
changed = true
} else if (ins.immediate == 65535 && ins.type == IRDataType.WORD) {
chunk.instructions.removeAt(idx)
changed = true
when (ins.immediate) {
0 -> {
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
changed = true
}
255 if ins.type == IRDataType.BYTE -> {
chunk.instructions.removeAt(idx)
changed = true
}
65535 if ins.type == IRDataType.WORD -> {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
Opcode.OR -> {
@@ -477,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 {
@@ -173,7 +219,7 @@ class IRUnusedCodeRemover(
if(chunk!=null)
new+=chunk
else
allLabeledChunks[label]?.let { new += it }
allLabeledChunks[label]?.let { c -> new += c }
}
else
new += instr.branchTarget!!
@@ -226,7 +272,7 @@ class IRUnusedCodeRemover(
chunk.instructions.forEach {
if(it.labelSymbol!=null) {
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
allLabeledChunks[chunkName]?.let { linkedChunks+=it }
allLabeledChunks[chunkName]?.let { c -> linkedChunks += c }
}
}
}

View File

@@ -1,22 +1,31 @@
package prog8.codegen.intermediate
import prog8.code.core.AssemblyError
import prog8.intermediate.IRDataType
internal class RegisterPool {
// reserve 0,1,2 for return values of subroutine calls and syscalls in IR assembly code
private var firstFree: Int=3
private var firstFreeFloat: Int=3
// everything from 99000 onwards is reserved for special purposes:
// 99000 - 99099 : WORD registers for syscall arguments and response value(s)
// 99100 - 99199 : BYTE registers for syscall arguments and response value(s)
fun peekNext() = firstFree
fun peekNextFloat() = firstFreeFloat
private var nextRegister: Int=1
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
fun nextFree(): Int {
val result = firstFree
firstFree++
return result
fun getTypes(): Map<Int, IRDataType> = registerTypes
init {
for(i in 99000..99099)
registerTypes[i] = IRDataType.WORD
for(i in 99100..99199)
registerTypes[i] = IRDataType.BYTE
}
fun nextFreeFloat(): Int {
val result = firstFreeFloat
firstFreeFloat++
fun next(type: IRDataType): Int {
if(nextRegister>=99000)
throw AssemblyError("register pool depleted")
val result = nextRegister
nextRegister++
registerTypes[result] = type
return result
}
}

View File

@@ -0,0 +1,149 @@
package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.core.DataType
import prog8.intermediate.*
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
val st = IRSymbolTable()
if (sourceSt != null) {
sourceSt.flat.forEach {
when(it.value.type) {
StNodeType.STATICVAR -> st.add(convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
StNodeType.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 -> { }
}
}
st.validate()
st.allVariables().forEach { variable ->
variable.onetimeInitializationArrayValue?.let {
it.forEach { arrayElt ->
val addrOfSymbol = arrayElt.addressOfSymbol
if (addrOfSymbol != null) {
require(addrOfSymbol.contains('.')) {
"pointer var in array should be properly scoped: $addrOfSymbol in ${variable.name}"
}
}
}
}
}
}
return st
}
private fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
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,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
variable.initializationArrayValue?.map { convertArrayElt(it) },
variable.length,
variable.zpwish,
variable.align,
variable.dirty)
} else {
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
if(array==null)
return null
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
} else {
newArray.add(convertArrayElt(it))
}
}
return newArray
}
val scopedName = variable.scopedNameString
return IRStStaticVariable(scopedName,
variable.dt,
variable.initializationNumericValue,
variable.initializationStringValue,
fixupAddressOfInArray(variable.initializationArrayValue),
variable.length,
variable.zpwish,
variable.align,
variable.dirty
)
}
}
private fun convert(variable: StMemVar): IRStMemVar {
if('.' in variable.name) {
return IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
)
} else {
val scopedName = try {
variable.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
}
}
private fun convert(constant: StConstant): IRStConstant {
val dt = DataType.forDt(constant.dt)
val scopedName = if('.' in constant.name) {
constant.name
} else {
try {
constant.scopedNameString
} catch (_: UninitializedPropertyAccessException) {
constant.name
}
}
return IRStConstant(scopedName, dt, constant.value)
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
else
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,8 +3,8 @@ package prog8.codegen.vm
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.code.core.ICodeGeneratorBackend
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
@@ -19,12 +19,18 @@ class VmCodeGen: ICodeGeneratorBackend {
): IAssemblyProgram {
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
return VmAssemblyProgram(irProgram.name, irProgram)
}
}
internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
internal class VmAssemblyProgram(
override val name: String,
internal val irProgram: IRProgram
): IAssemblyProgram {
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
// the VM reads the IR file from disk.

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,6 +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 printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }

View File

@@ -9,7 +9,7 @@ class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="main.start")
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY)
chunks.forEach { sub += it }
block += sub
val target = VMTarget()
@@ -21,8 +21,9 @@ class TestIRPeepholeOpt: FunSpec({
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
romable = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
val prog = IRProgram("test", IRSymbolTable(), options, target)
@@ -52,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)
@@ -68,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

@@ -23,8 +23,9 @@ class TestVmCodeGen: FunSpec({
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
romable = false,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
}
@@ -45,13 +46,14 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"pi",
DataType.forDt(BaseDataType.UBYTE),
DataType.UBYTE,
ZeropageWish.DONTCARE,
0u,
PtNumber(BaseDataType.UBYTE, 0.0, Position.DUMMY),
false,
null,
null,
Position.DUMMY
))
@@ -60,6 +62,7 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
@@ -69,23 +72,25 @@ class TestVmCodeGen: FunSpec({
DataType.arrayFor(BaseDataType.UBYTE),
ZeropageWish.DONTCARE,
0u,
false,
null,
3u,
Position.DUMMY
))
sub.add(PtVariable(
"xx",
DataType.forDt(BaseDataType.WORD),
DataType.WORD,
ZeropageWish.DONTCARE,
0u,
PtNumber(BaseDataType.WORD, 1.0, Position.DUMMY),
false,
null,
null,
Position.DUMMY
))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.forDt(BaseDataType.UBYTE), Position.DUMMY).also { idx ->
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX",
DataType.arrayFor(BaseDataType.UBYTE),
Position.DUMMY))
@@ -93,7 +98,7 @@ class TestVmCodeGen: FunSpec({
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.forDt(BaseDataType.UBYTE), Position.DUMMY)
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX",
DataType.arrayFor(BaseDataType.UBYTE),
Position.DUMMY))
@@ -104,15 +109,15 @@ class TestVmCodeGen: FunSpec({
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
prefixAssign.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
numberAssign.add(PtNumber(BaseDataType.WORD, 42.0, Position.DUMMY))
@@ -120,10 +125,10 @@ class TestVmCodeGen: FunSpec({
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.forDt(BaseDataType.WORD), Position.DUMMY))
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)
cxregAssign.add(PtIdentifier("cx16.r0", DataType.forDt(BaseDataType.UWORD), Position.DUMMY))
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
sub.add(cxregAssign)
block.add(sub)
@@ -131,7 +136,7 @@ class TestVmCodeGen: FunSpec({
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.forDt(BaseDataType.UWORD), 100u, null, Position.DUMMY))
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
val options = getTestOptions()
@@ -160,43 +165,44 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"f1",
DataType.forDt(BaseDataType.FLOAT),
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp3.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY))
if3.add(cmp3)
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp4.add(PtNumber(BaseDataType.FLOAT, 0.0, Position.DUMMY))
if4.add(cmp4)
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
@@ -231,43 +237,44 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"f1",
DataType.forDt(BaseDataType.FLOAT),
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp3.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if3.add(cmp3)
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp4.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if4.add(cmp4)
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
@@ -298,27 +305,28 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"f1",
DataType.forDt(BaseDataType.FLOAT),
DataType.FLOAT,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) })
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.forDt(BaseDataType.FLOAT), Position.DUMMY))
val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.FLOAT, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) })
@@ -353,43 +361,44 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"sb1",
DataType.forDt(BaseDataType.BYTE),
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp3.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY))
if3.add(cmp3)
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp4.add(PtNumber(BaseDataType.BYTE, 0.0, Position.DUMMY))
if4.add(cmp4)
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
@@ -424,43 +433,44 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"sb1",
DataType.forDt(BaseDataType.BYTE),
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression("!=", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp2 = PtBinaryExpression("!=", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if2)
val if3 = PtIfElse(Position.DUMMY)
val cmp3 = PtBinaryExpression("<", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp3 = PtBinaryExpression("<", DataType.BOOL, Position.DUMMY)
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp3.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY))
if3.add(cmp3)
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
sub.add(if3)
val if4 = PtIfElse(Position.DUMMY)
val cmp4 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.forDt(BaseDataType.BYTE), Position.DUMMY))
val cmp4 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
cmp4.add(PtNumber(BaseDataType.BYTE, 42.0, Position.DUMMY))
if4.add(cmp4)
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
@@ -491,27 +501,28 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"ub1",
DataType.forDt(BaseDataType.BYTE),
DataType.BYTE,
ZeropageWish.DONTCARE,
0u,
false,
null,
null,
Position.DUMMY
))
val if1 = PtIfElse(Position.DUMMY)
val cmp1 = PtBinaryExpression("==", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp1.add(PtIdentifier("main.start.ub1", DataType.forDt(BaseDataType.UBYTE), Position.DUMMY))
val cmp1 = PtBinaryExpression("==", DataType.BOOL, Position.DUMMY)
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp1.add(PtNumber(BaseDataType.UBYTE, 42.0, Position.DUMMY))
if1.add(cmp1)
if1.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also { it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) })
if1.add(PtNodeGroup())
sub.add(if1)
val if2 = PtIfElse(Position.DUMMY)
val cmp2 = PtBinaryExpression(">", DataType.forDt(BaseDataType.BOOL), Position.DUMMY)
cmp2.add(PtIdentifier("main.start.ub1", DataType.forDt(BaseDataType.UBYTE), Position.DUMMY))
val cmp2 = PtBinaryExpression(">", DataType.BOOL, Position.DUMMY)
cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
cmp2.add(PtNumber(BaseDataType.UBYTE, 42.0, Position.DUMMY))
if2.add(cmp2)
if2.add(PtNodeGroup().also { it.add(PtJump(Position.DUMMY).also {it.add(PtNumber(BaseDataType.UWORD, 0xc000.toDouble(), Position.DUMMY)) }) })
@@ -541,8 +552,8 @@ class TestVmCodeGen: FunSpec({
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(extsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.forDt(BaseDataType.UNDEFINED), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
sub.add(call)
block.add(sub)
program.add(block)
@@ -553,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.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
@@ -23,4 +21,4 @@ sourceSets {
}
}
// note: there are no unit tests in this module!
// note: there are no unit tests in this module!

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)

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