Compare commits

...
78 Commits
Author SHA1 Message Date
Irmen de Jong 3ea8ca59b7 beta 4 now with support for long values 2025-10-04 22:55:18 +02:00
Irmen de Jong 71ffbe2ba7 document the long type 2025-10-04 22:33:43 +02:00
Irmen de Jong e63921009c added math.mul32(), verafx.muls now returns long 2025-10-04 21:54:53 +02:00
Irmen de Jong db1aa3f257 math.crc32() now returns the crc value as a long 2025-10-04 17:40:49 +02:00
Irmen de Jong 8abdb837b2 fix long equality comparisons 2025-10-04 17:21:39 +02:00
Irmen de Jong cdeabd4b66 only * and / 2025-10-04 05:13:18 +02:00
Irmen de Jong efff74c0f1 added sys.pushl() and sys.popl() 2025-10-04 03:05:50 +02:00
Irmen de Jong 0e177cb531 added cbm.RDTIML and cbm.SETTIML wrappers 2025-10-04 02:00:24 +02:00
Irmen de Jong 35b921b062 added bcd module for decimal addition and subtraction using BCD mode 2025-10-03 23:36:17 +02:00
Irmen de Jong a7d98e43b8 cmp(long, long) 2025-10-03 22:41:59 +02:00
Irmen de Jong 845ee2dd83 smaller code for several long operations 2025-10-03 21:25:13 +02:00
Irmen de Jong f9b0bfe31b implement rol() and ror() on longs (also rol2 and ror2 and abs) 2025-10-03 01:22:24 +02:00
Irmen de Jong f303d15628 fix long equality checks 2025-10-03 01:22:24 +02:00
Irmen de Jong 67f7ffa52d multi value returns with longs 2025-10-03 01:22:24 +02:00
Irmen de Jong 88c5d9783a long params and return values 2025-10-03 01:22:24 +02:00
Irmen de Jong e7fc0360ad long bitwise operator expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong cd1862dd9f fix signed long shift right 2025-10-03 01:22:24 +02:00
Irmen de Jong 045c4909a8 long equality comparisons 2025-10-03 01:22:24 +02:00
Irmen de Jong f1bfe619b2 avoiding endless loop in optimizer 2025-10-03 01:22:24 +02:00
Irmen de Jong e0107bacbd implement long << >> expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong b3bd2a6a09 fixed a bunch of long type handling 2025-10-03 01:22:24 +02:00
Irmen de Jong ff1f58e022 implement simple + / - long expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong 557b12668d implement print_ulhex(long, prefix) 2025-10-03 01:22:24 +02:00
Irmen de Jong b058f1c7c2 implement mklong(a,b,c,d) and mklong2(w1,w2) 2025-10-03 01:22:24 +02:00
Irmen de Jong 3e07b6ca70 adding long arrays 2025-10-03 01:22:24 +02:00
Irmen de Jong d66dc664de work on longs 2025-10-03 01:22:24 +02:00
Irmen de Jong a2b9d78cf3 start with introducing LONG datatype (32 bits signed integer) 2025-10-03 01:22:24 +02:00
Irmen de Jong 44f70da113 upgrade to JDK 17 for the build process (required by Gradle 9) 2025-10-03 00:18:55 +02:00
Irmen de Jong c87eaf576d buildfix and update to Gradle 9.1.0 2025-10-03 00:11:52 +02:00
Irmen de Jong c09b1395f6 IR: add a unit test for the SSA basic block change 2025-10-01 22:49:51 +02:00
Irmen de Jong d6a8201291 IR: fix SQRT reg1 type 2025-10-01 20:28:38 +02:00
Irmen de Jong 4187f97f7a IR: rename <CODE> to <CHUNK> , put the code lines in a new sub node <CODE> for improved xml structure 2025-10-01 19:32:05 +02:00
Irmen de Jong 9d3b2f12fd IR code blocks now better SSA basic blocks (ending with single branch instruction) 2025-10-01 19:08:44 +02:00
Irmen de Jong 87c1bbbf40 readme about LSP 2025-10-01 01:15:00 +02:00
Irmen de Jong a7ad6abdb9 Merge branch 'languageServer'
# Conflicts:
#	examples/test.p8
2025-09-30 22:14:03 +02:00
Irmen de Jong a611406020 IR: add compilerversion attribute to p8ir file 2025-09-30 22:07:04 +02:00
Irmen de Jong 75da38224d IR: make LSIG,MSIG,CONCAT instruction set flags to skip cmp #0 afterwards (if msb(x)>0) 2025-09-30 21:48:57 +02:00
Irmen de Jong 8d6f3301c8 conv.any2uword() return values have been changed to be more useful and convenient
now returns both the actual value and the number of characters, and the return values for the virtual target now matches the others.
2025-09-30 20:44:02 +02:00
Irmen de Jong 86b52a1c5e fix endless loop in rewriting type of const long values 2025-09-29 22:28:13 +02:00
Irmen de Jong 2c8b1c2022 moved cx16.cpu_is_65816() to sys.cpu_is_65816(). It know also does proper detection on the C64 and C128 like on the X16, because those two computers can also have this CPU via a SuperCPU expansion. 2025-09-29 21:37:26 +02:00
Irmen de Jong ab1f065752 first setup of LSP languageserver 2025-09-27 15:27:37 +02:00
Irmen de Jong 2c7256a443 support assignment to indexed pointer targets 2025-09-27 14:52:04 +02:00
Irmen de Jong 97420b28e5 preparing to support assignment to indexed pointer targets 2025-09-27 11:03:55 +02:00
Irmen de Jong 1467c7039d fix pointer deref ast printer 2025-09-27 10:42:55 +02:00
Irmen de Jong d319badc6c default 0 for long type 2025-09-25 21:32:23 +02:00
Irmen de Jong 65d6c1c438 fix IR instruction R/W usage counter 2025-09-25 01:50:27 +02:00
Irmen de Jong abeefb5655 improved pointer[0] 2025-09-24 22:26:09 +02:00
Irmen de Jong 50fecbcebe c64 sprite multiplexer WIP 2025-09-24 21:11:08 +02:00
Irmen de Jong 4fe8b72d42 fix broken uword comparison and asm peephole optimization 2025-09-24 02:42:26 +02:00
Irmen de Jong f3b060df51 no longer save Y register when loading value through ZP pointer LDA (zp),Y 2025-09-23 22:59:01 +02:00
Irmen de Jong 09d1cb6925 fix crash when indexing on a label or subroutine name
working on multiplexer
2025-09-23 20:07:52 +02:00
Irmen de Jong 54fa72fa98 added sys.waitrasterline() routine like sys.waitvsync() but wait for a given raster line
optimize uword <= $xx00 into msb(uword)<$xx
2025-09-23 01:09:54 +02:00
Irmen de Jong fd62fe7511 fix crash on invalid type cast added to ptr deref expression ('.' operator) 2025-09-22 23:54:01 +02:00
Irmen de Jong bfb34dff62 fix recursive var decl error in case of some pointer derefs 2025-09-22 23:45:59 +02:00
Irmen de Jong 817b623596 optimize uword >= $xx00 into msb(uword)>=$xx 2025-09-22 22:59:56 +02:00
Irmen de Jong f6dbeb1f63 working on a more complex c64 sprite multiplexer 2025-09-22 21:58:12 +02:00
Irmen de Jong c4b9bdd33f proper pointer array initializer size checking, nicer sprite movement in simplemultiplexer 2025-09-22 20:07:38 +02:00
Irmen de Jong 68e0d5f1b5 add sys.set_rasterline on c64 and c128 targets as well (cx16 already had it) 2025-09-21 01:36:43 +02:00
Irmen de Jong 4939e3df55 add sys.update_rasterirq, more robust sei/cli handling, added simple c64 sprite multiplexer example 2025-09-21 00:28:33 +02:00
Irmen de Jong 19f19f3880 doc and opening borders 2025-09-20 21:44:48 +02:00
Irmen de Jong ad0c767ea8 %breakpoint! introduced to place after an assignment to make it parse correctly 2025-09-20 00:46:15 +02:00
Irmen de Jong ed5f4d5855 fix missing subroutine argument list check 2025-09-19 05:35:03 +02:00
Irmen de Jong c2f5d37486 new cx16/charfade.p8 example 2025-09-19 04:44:08 +02:00
Irmen de Jong 231b50dacb optimize certain word multiplication with bit shifting if it's a power of 2 2025-09-18 20:57:26 +02:00
Irmen de Jong a71895cbe8 optimize pointer.field += 1 into pointer.field INC/DEC 2025-09-18 19:27:36 +02:00
Irmen de Jong a8bede17b2 fix defer() with the arena allocator ("return values are evaluated before the defer is executed") 2025-09-17 23:59:32 +02:00
Irmen de Jong f6c8e693a5 add offsetof() 2025-09-17 23:30:15 +02:00
Irmen de Jong 9461e4088c fixed the hashtable example and workarounds for misbehaving defer in allocators 2025-09-17 22:53:01 +02:00
Irmen de Jong efd73fd10d implement some more in place pointer operators 2025-09-17 18:19:03 +02:00
Irmen de Jong ddf6e84a1a fix type checks for wrong pointer types in pointer array initalizer and assignment 2025-09-16 21:18:29 +02:00
Irmen de Jong 633d6c34e2 add support for struct pointers and short-form initializers in arrays and assignments 2025-09-16 20:37:50 +02:00
Irmen de Jong 6e7fbc6683 fix crash when invalid struct name is used in pointer decl 2025-09-15 20:39:21 +02:00
Irmen de Jong 124ea1230b fix vm to understand struct instances in arrays 2025-09-14 19:04:01 +02:00
Irmen de Jong 8b48a295b6 allow struct initializers to occur in array literals 2025-09-14 18:16:46 +02:00
Irmen de Jong d285d37fdb fix clobbers syntax in symboldumper 2025-09-13 21:47:52 +02:00
Irmen de Jong 8bb927b483 fix compiler crash with on..call statement in nested scope 2025-09-13 06:44:49 +02:00
Irmen de Jong 1af4cd0d63 fix struct initializer error checking 2025-09-12 19:55:12 +02:00
Irmen de Jong db2f28c4cd add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)
fix nullpointer in array initalizer
2025-09-12 17:09:44 +02:00
165 changed files with 8210 additions and 1751 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# An .aiignore file follows the same syntax as a .gitignore file.
# .gitignore documentation: https://git-scm.com/docs/gitignore
# you can ignore files
.DS_Store
*.log
*.tmp
*.bin
*.prg
# or folders
dist/
build/
out/
output/
.gradle/
.kotlin/
+2 -2
View File
@@ -20,10 +20,10 @@ jobs:
make -j4
sudo make install
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: temurin
- name: Build and test with Gradle
+1
View File
@@ -15,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.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" />
+234
View File
@@ -0,0 +1,234 @@
; Binary Search Tree.
; It's a simple implementation for test/demonstration purposes of the pointer support;
; no balancing is done and memory is not freed when elements are removed.
%import textio
btree {
sub benchmark(uword max_time) -> uword {
txt.nl()
cbm.SETTIM(0,0,0)
uword score
while cbm.RDTIM16() < max_time {
bench_operations()
txt.chrout('.')
score++
}
txt.nl()
return score
}
sub bench_operations() {
arena.freeall()
btree.root = 0
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
btree.add(cx16.r0)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
void btree.contains(203)
void btree.contains(204)
void btree.contains(605)
void btree.contains(606)
btree.remove(9999)
btree.remove(97)
btree.remove(187)
btree.remove(203)
btree.remove(275)
btree.remove(321)
btree.remove(520)
btree.remove(562)
btree.remove(606)
btree.remove(719)
btree.remove(794)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
}
struct Node {
^^Node left
^^Node right
uword value
}
^^Node root = 0
sub add(uword value) {
^^Node node = arena.alloc(sizeof(Node))
node.value = value
node.left = node.right = 0
if root==0
root=node
else {
^^Node parent = root
repeat {
if parent.value >= value {
if parent.left!=0
parent = parent.left
else {
parent.left = node
return
}
} else {
if parent.right!=0
parent = parent.right
else {
parent.right = node
return
}
}
}
}
}
sub contains(uword value) -> bool {
^^Node r = root
while r!=0 {
if r.value==value
return true
if r.value>value
r = r.left
else
r = r.right
}
return false
}
sub size() -> ubyte {
ubyte count
if root!=0
count_node(root)
return count
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
count_node(r.left)
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
count_node(r.right)
r = sys.popw()
}
}
}
sub remove(uword value) {
; note: we don't deallocate the memory from the node, for simplicity sake
^^Node n = root
^^Node parent = 0
while n!=0 {
if n.value==value {
if n.left==0
replacechild(parent, n, n.right)
else if n.right==0
replacechild(parent, n, n.left)
else {
; Both left & right subtrees are present.
; N = node to delete.
; Find N's successor S. (N's right subtree's minimum element)
; Attach N's left subtree to S.left (S doesn't have a left child)
; Attach N's right subtree to Parent in place of N.
^^Node successor = find_successor(n)
successor.left = n.left
replacechild(parent, n, n.right)
}
return
}
parent = n
if n.value>value
n = n.left
else
n = n.right
}
sub find_successor(^^Node p) -> ^^Node {
^^Node succ = p
p = p.right
while p!=0 {
succ = p
p = p.left
}
return succ
}
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
if p.left==child
p.left = newchild
else
p.right = newchild
}
}
sub process_tree_inorder() {
if root!=0
process_tree(root)
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
process_tree(r.left)
r = sys.popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
process_tree(r.right)
r = sys.popw()
}
}
}
sub process_tree_preorder() {
if root!=0
process_tree(root,0)
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
}
}
}
}
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}
+10 -9
View File
@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -67,10 +68,6 @@ main {
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
@@ -79,13 +76,17 @@ main {
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
announce_benchmark("btree-struct-pointers")
benchmark_score[benchmark_number] = btree.benchmark(200)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
@@ -93,14 +94,14 @@ main {
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
txt.print("\n\ntotal score : ")
txt.print_uw(total_score)
txt.print(" (higher=better)\n")
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name
+2 -1
View File
@@ -5,9 +5,10 @@ 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"
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
// all automatically generated labels everywhere need to have the same label name prefix:
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
@@ -37,6 +37,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val returns: ReturnConvention = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
@@ -45,6 +46,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R0R1_32)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
@@ -61,6 +63,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val paramConv = when(val paramType = actualParamTypes[0]) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R0R1_32, false)
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
@@ -88,10 +91,10 @@ class FSignature(val pure: Boolean, // does it have side effects?
val BuiltinFunctions: Map<String, FSignature> = mapOf(
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns a status in the carry flag, but not a proper return value
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
@@ -100,9 +103,11 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
@@ -116,21 +121,26 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
@@ -13,6 +13,7 @@ class CompilationOptions(val output: OutputType,
val noSysInit: Boolean,
val romable: Boolean,
val compTarget: ICompilationTarget,
val compilerVersion: String,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var memtopAddress: UInt,
+35 -3
View File
@@ -50,8 +50,10 @@ enum class BaseDataType {
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
@@ -143,7 +145,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
@@ -222,6 +224,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -284,6 +287,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
@@ -344,6 +348,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isUnsignedWord = base == BaseDataType.UWORD
val isSignedWord = base == BaseDataType.WORD
val isInteger = base.isInteger
val isWordOrByteOrBool = base.isWordOrByteOrBool
val isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
@@ -362,6 +367,7 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
@@ -394,7 +400,9 @@ enum class RegisterOrPair {
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1_32, R2R3_32, R4R5_32, R6R7_32, R8R9_32, R10R11_32, R12R13_32, R14R15_32;
companion object {
val names by lazy { entries.map { it.toString()} }
@@ -407,6 +415,18 @@ enum class RegisterOrPair {
}
}
fun startregname() = when(this) {
R0R1_32 -> "r0"
R2R3_32 -> "r2"
R4R5_32 -> "r4"
R6R7_32 -> "r6"
R8R9_32 -> "r8"
R10R11_32 -> "r10"
R12R13_32 -> "r12"
R14R15_32 -> "r14"
else -> throw IllegalArgumentException("must be a combined virtual register $this")
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
@@ -421,12 +441,13 @@ enum class RegisterOrPair {
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, null -> ""
else -> throw IllegalArgumentException("invalid register param type")
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
}
return listOf("cx16", name.lowercase()+suffix)
}
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
fun isLong() = this in combinedLongRegisters
} // only used in parameter and return value specs in asm subroutines
@@ -463,6 +484,17 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val combinedLongRegisters = arrayOf(
RegisterOrPair.R0R1_32,
RegisterOrPair.R2R3_32,
RegisterOrPair.R4R5_32,
RegisterOrPair.R6R7_32,
RegisterOrPair.R8R9_32,
RegisterOrPair.R10R11_32,
RegisterOrPair.R12R13_32,
RegisterOrPair.R14R15_32
)
val CpuRegisters = arrayOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
@@ -14,6 +14,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
@@ -252,6 +252,14 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
newValue.add(newAddr)
}
}
is PtBuiltinFunctionCall -> {
// could be a struct instance or memory slab "allocation"
if (elt.name != "prog8_lib_structalloc" && elt.name != "memory")
throw AssemblyError("weird array value element $elt")
else {
newValue.add(elt)
}
}
else -> throw AssemblyError("weird array value element $elt")
}
}
@@ -550,7 +558,7 @@ class AsmGen6502Internal (
} else {
return if (allocator.isZpVar((target as PtNamedNode).scopedName)) {
// pointervar is already in the zero page, no need to copy
loadAFromZpPointerVar(sourceName, true)
loadAFromZpPointerVar(sourceName)
sourceName
} else {
out("""
@@ -609,7 +617,7 @@ class AsmGen6502Internal (
}
}
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean) {
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean=false) {
if (isTargetCpu(CpuType.CPU65C02))
out(" lda ($zpPointerVar)")
else {
@@ -798,6 +806,7 @@ class AsmGen6502Internal (
RegisterOrPair.AY,
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
in combinedLongRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
else -> throw AssemblyError("invalid register")
@@ -805,6 +814,9 @@ class AsmGen6502Internal (
}
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
// this is basically the fallback assignment routine, it is RARELY called
when {
target.datatype.isByteOrBool -> {
if (value.asConstInteger()==0) {
@@ -852,6 +864,98 @@ class AsmGen6502Internal (
), value.definingISub()
)
}
target.datatype.isLong -> {
if(value is PtNumber) {
val hex = value.asConstInteger()!!.toString(16).padStart(8, '0')
when(target.kind) {
TargetStorageKind.VARIABLE -> {
out("""
lda #${hex.substring(6,8)}
sta ${target.asmVarname}
lda #${hex.substring(4, 6)}
sta ${target.asmVarname}+1
lda #${hex.substring(2, 4)}
sta ${target.asmVarname}+2
lda #${hex.take(2)}
sta ${target.asmVarname}+3""")
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value is PtIdentifier && value.type.isLong) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
val valuesym = asmSymbolName(value)
out(
"""
lda $valuesym
sta ${target.asmVarname}
lda $valuesym+1
sta ${target.asmVarname}+1
lda $valuesym+2
sta ${target.asmVarname}+2
lda $valuesym+3
sta ${target.asmVarname}+3"""
)
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value is PtTypeCast && value.type.isLong) {
if(value.value is PtIdentifier) {
val valuesym = asmSymbolName((value.value as PtIdentifier).name)
if(value.value.type.isByte) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
out(" lda $valuesym | sta ${target.asmVarname}")
signExtendLongVariable(target.asmVarname, value.value.type.base)
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else if(value.value.type.isWord) {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
out("""
lda $valuesym
sta ${target.asmVarname}
lda $valuesym+1
sta ${target.asmVarname}+1""")
signExtendLongVariable(target.asmVarname, value.value.type.base)
}
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in combinedLongRegisters)
val startreg = target.register.toString().take(2).lowercase()
out("""
lda $valuesym
sta cx16.$startreg
lda $valuesym+1
sta cx16.$startreg+1""")
signExtendLongVariable("cx16.$startreg", value.value.type.base)
}
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
} else throw AssemblyError("weird casted type")
} else {
TODO("assign typecasted expression $value to a long target ${target.position} - use simple expressions and temporary variables for now")
}
} else {
TODO("assign long expression $value to a target ${target.position} - use simple expressions and temporary variables for now")
}
}
target.datatype.isFloat -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignRegister(RegisterOrPair.FAC1, target)
@@ -1182,7 +1286,7 @@ $repeatLabel""")
if(returnvalue!=null) {
val returnDt = sub.signature.returns.single()
if (returnDt.isNumericOrBool || returnDt.isPointer) {
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!)
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!, returnDt.isSigned)
}
else {
// all else take its address and assign that also to AY register pair
@@ -1203,7 +1307,7 @@ $repeatLabel""")
assignExpressionTo(it.first as PtExpression, tgt)
}
assigns.first().also {
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!)
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!, (it.first as PtExpression).type.isSigned)
}
}
out(" rts")
@@ -1289,6 +1393,50 @@ $repeatLabel""")
}
}
internal fun signExtendLongVariable(asmvar: String, valueDt: BaseDataType) {
// sign extend signed word in a var to a full long in that variable
when(valueDt) {
BaseDataType.UBYTE -> {
if(isTargetCpu(CpuType.CPU65C02)) {
out("""
stz $asmvar+1
stz $asmvar+2
stz $asmvar+3""")
}
else {
out("""
lda #0
sta $asmvar+1
sta $asmvar+2
sta $asmvar+3""")
}
}
BaseDataType.BYTE -> {
out("""
lda $asmvar
ora #$7f
bmi +
lda #0
+ sta $asmvar+1
sta $asmvar+2
sta $asmvar+3""")
}
BaseDataType.UWORD -> {
out(" lda #0 | sta $asmvar+2 | sta $asmvar+3")
}
BaseDataType.WORD -> {
out("""
lda $asmvar+1
ora #$7f
bmi +
lda #0
+ sta $asmvar+2
sta $asmvar+3""")
}
else -> throw AssemblyError("need byte or word type")
}
}
internal fun isZpVar(variable: PtIdentifier): Boolean = allocator.isZpVar(variable.name)
internal fun jmp(asmLabel: String, indirect: Boolean=false, indexedX: Boolean=false) {
@@ -463,15 +463,19 @@ private fun optimizeStoreLoadSame(
mods.add(Modification(lines[2].index, true, null))
}
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated IF NOT IO ADDRESS
if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
) {
val firstVal = first.substring(4).trimStart()
val thirdVal = third.substring(4).trimStart()
if (firstVal == thirdVal)
mods.add(Modification(lines[3].index, true, null))
if (firstVal == thirdVal) {
val address = getAddressArg(third, symbolTable)
if (address != null && !machine.isIOAddress(address)) {
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
return mods
@@ -1,5 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
@@ -25,15 +27,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val sscope = fcall.definingISub()
when (fcall.name) {
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
"msw" -> funcMsw(fcall, resultRegister)
"lsw" -> funcLsw(fcall, resultRegister)
"msb" -> funcMsb(fcall, resultRegister)
"lsb" -> funcLsb(fcall, resultRegister)
"mkword" -> funcMkword(fcall, resultRegister)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(fcall, resultRegister)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
"mklong", "mklong2" -> funcMklong(fcall) // result is in R0:R1
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(fcall, resultRegister)
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(fcall, resultRegister)
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(fcall, resultRegister)
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
"sgn" -> funcSgn(fcall, resultRegister, sscope)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
"divmod__ubyte" -> funcDivmod(fcall)
@@ -46,10 +49,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"setmsb" -> funcSetLsbMsb(fcall, true)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekl" -> funcPeekL(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peekbool" -> funcPeekBool(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokel" -> funcPokeL(fcall)
"pokef" -> funcPokeF(fcall)
"pokemon" -> {
val memread = PtMemoryByte(fcall.position)
@@ -344,8 +349,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else {
// arg1 is a word
} else if(arg1.type.isWord) {
if(arg2.type.isWord) {
when (arg2) {
is PtIdentifier -> {
@@ -376,6 +380,60 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else if(arg1.type.isLong) {
if(arg2.type.isLong) {
if(arg1 is PtIdentifier && arg2 is PtNumber) {
val var1 = asmgen.asmVariableName(arg1)
val hex = arg2.number.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $var1
cmp #${hex.substring(6, 8)}
bne +
lda $var1+1
cmp #${hex.substring(4, 6)}
bne +
lda $var1+2
cmp #${hex.substring(2, 4)}
bne +
lda $var1+3
cmp #${hex.take(2)}
+""")
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
val var1 = asmgen.asmVariableName(arg1)
val var2 = asmgen.asmVariableName(arg2)
asmgen.out("""
lda $var1
cmp $var2
bne +
lda $var1+1
cmp $var2+1
bne +
lda $var1+2
cmp $var2+2
bne +
lda $var1+3
cmp $var2+3
+""")
} else {
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R2R3_32, true)
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R0R1_32, true)
asmgen.out("""
lda cx16.r0
cmp cx16.r2
bne +
lda cx16.r0+1
cmp cx16.r2+1
bne +
lda cx16.r0+2
cmp cx16.r2+2
bne +
lda cx16.r0+3
cmp cx16.r2+3
+""")
}
// TODO: carry flag is not correct......
} else
throw AssemblyError("args for cmp() should have same dt")
}
}
@@ -385,7 +443,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val name = (fcall.args[0] as PtString).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
val slabname = PtIdentifier("$StMemorySlabBlockName.memory_$name", DataType.UWORD, fcall.position)
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position)
addressOf.add(slabname)
addressOf.parent = fcall
@@ -399,9 +457,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
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 prefix = if(fcall.args.isEmpty()) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
val labelname = PtIdentifier("$prefix.${SymbolTable.labelnameForStructInstance(fcall)}", fcall.type, fcall.position)
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position))
addressOf.add(labelname)
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)
@@ -421,6 +480,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
BaseDataType.LONG -> TODO("sqrt LONG")
BaseDataType.FLOAT -> {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
@@ -477,6 +537,26 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("ror2 long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out("""
lsr $variable+3
ror $variable+2
ror $variable+1
ror $variable
bcc +
lda $variable+3
ora #$80
sta $variable+3
+""")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -541,6 +621,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("ror long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+3 | ror $variable+2 | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -592,6 +683,24 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("rol2 long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out("""
asl $variable
rol $variable+1
rol $variable+2
rol $variable+3
bcc +
inc $variable
+""")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -655,6 +764,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("weird type")
}
}
BaseDataType.LONG -> {
// a bit strange, rotating a signed type, but it has to do for now
when(what) {
is PtArrayIndexer -> TODO("rol long array ${what.position}")
is PtIdentifier -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1 | rol $variable+2 | rol $variable+3")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
@@ -726,6 +846,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
BaseDataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
BaseDataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
BaseDataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
BaseDataType.LONG -> TODO("sgn LONG")
BaseDataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
@@ -744,6 +865,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, scope, asmgen), RegisterOrPair.AY)
}
BaseDataType.LONG -> {
asmgen.out(" jsr prog8_lib.abs_l_into_R0R1")
assignAsmGen.assignRegisterLong(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.R0R1_32, true, fcall.position, scope, asmgen), RegisterOrPair.R0R1_32)
}
BaseDataType.FLOAT -> {
asmgen.out(" jsr floats.func_abs_f_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
@@ -881,6 +1006,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPokeL(fcall: PtBuiltinFunctionCall) {
val addrExpr = fcall.args[0]
val value = fcall.args[1]
TODO("pokel $addrExpr, $value")
}
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr floats.MOVFM")
@@ -977,6 +1108,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPeekL(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val addrExpr = fcall.args[0]
val value = fcall.args[1]
TODO("peekl $addrExpr, $value")
}
private fun funcClamp(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type.isSigned
when {
@@ -996,6 +1134,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
assignAsmGen.assignRegisterpairWord(targetReg, RegisterOrPair.AY)
}
fcall.type.isLong -> {
TODO("clamp long ${fcall.position}")
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -1052,6 +1193,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
fcall.type.isLong -> {
TODO("min long ${fcall.position}")
}
else -> {
throw AssemblyError("min float not supported")
}
@@ -1060,55 +1204,109 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMax(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type.isSigned
if(fcall.type.isByte) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
asmgen.out("""
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
} else if(fcall.type.isWord) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
when {
fcall.type.isByte -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if (signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bmi +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
+"""
)
val targetReg = AsmAssignTarget.fromRegisters(
resultRegister ?: RegisterOrPair.A,
signed,
fcall.position,
fcall.definingISub(),
asmgen
)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
}
fcall.type.isWord -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if (signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bmi +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
}
val targetReg = AsmAssignTarget.fromRegisters(
resultRegister ?: RegisterOrPair.AY,
signed,
fcall.position,
fcall.definingISub(),
asmgen
)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
fcall.type.isLong -> {
TODO("max long ${fcall.position}")
}
else -> {
throw AssemblyError("max float not supported")
}
}
}
private fun funcMklong(fcall: PtBuiltinFunctionCall) {
// result long in R0:R1 (r0=lsw, r1=msw)
fun isArgRegister(expression: PtExpression, reg: RegisterOrPair): Boolean {
if(expression !is PtIdentifier)
return false
return expression.name.startsWith("cx16.${reg.name.lowercase()}")
}
if(fcall.args.size==2) {
// mklong2(msw, lsw)
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1)) {
error("cannot use R0 and/or R1 as arguments for mklong2 because the result should go into R0:R1 ${fcall.position}")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1", DataType.UWORD)
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r0", DataType.UWORD)
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
} else {
throw AssemblyError("max float not supported")
// mklong(msb, b2, b1, lsb)
if(isArgRegister(fcall.args[0], RegisterOrPair.R0) || isArgRegister(fcall.args[0], RegisterOrPair.R1) ||
isArgRegister(fcall.args[1], RegisterOrPair.R0) || isArgRegister(fcall.args[1], RegisterOrPair.R1) ||
isArgRegister(fcall.args[2], RegisterOrPair.R0) || isArgRegister(fcall.args[2], RegisterOrPair.R1) ||
isArgRegister(fcall.args[3], RegisterOrPair.R0) || isArgRegister(fcall.args[3], RegisterOrPair.R1)) {
error("cannot use R0 and/or R1 as arguments for mklong because the result should go into R0:R1 ${fcall.position}")
} else {
assignAsmGen.assignExpressionToVariable(fcall.args[0], "cx16.r1H", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[1], "cx16.r1L", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[2], "cx16.r0H", DataType.UBYTE)
assignAsmGen.assignExpressionToVariable(fcall.args[3], "cx16.r0L", DataType.UBYTE)
}
}
}
@@ -1182,7 +1380,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isWord)
throw AssemblyError("msb required word argument")
throw AssemblyError("msb requires word argument")
if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is PtIdentifier) {
@@ -1368,6 +1566,53 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcMsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isLong)
throw AssemblyError("msw requires long argument")
if (arg is PtNumber)
throw AssemblyError("msw(const) should have been const-folded away")
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultRegister) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+2 | ldx $sourceName+3")
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName+2 | ldy $sourceName+3")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+2 | ldy $sourceName+3")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
asmgen.out(" lda $sourceName+2 | sta cx16.$regname | lda $sourceName+3 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
} else {
TODO("msw(expression) ${fcall.position} - use a temporary variable for now")
}
}
private fun funcLsw(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (!arg.type.isLong)
throw AssemblyError("lsw requires long argument")
if (arg is PtNumber)
throw AssemblyError("lsw(const) should have been const-folded away")
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultRegister) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
null, RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
asmgen.out(" lda $sourceName | sta cx16.$regname | lda $sourceName+1 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
} else {
TODO("lsw(expression) ${fcall.position} - use a temporary variable for now")
}
}
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map {
@@ -1410,6 +1655,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
val src = when {
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt==BaseDataType.LONG -> TODO("LONG argument for builtin func")
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
@@ -1428,6 +1674,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
conv.reg != null -> {
val src = when {
conv.dt==BaseDataType.FLOAT -> getSourceForFloat(value)
conv.dt==BaseDataType.LONG -> AsmAssignSource.fromAstSource(value, program, asmgen)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
@@ -1439,7 +1686,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
AsmAssignSource.fromAstSource(value, program, asmgen)
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, value.position, null, asmgen)
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, conv.dt.isSigned, value.position, null, asmgen)
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, value.position)
asmgen.translateNormalAssignment(assign, scope)
}
@@ -505,137 +505,158 @@ $endLabel""")
is StMemVar -> symbol.length!!
else -> 0u
}
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
fun iterateStrings() {
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("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$endLabel""")
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
} 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("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$indexVar .byte 0
$endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
}
fun iterateBytes() {
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<=255u) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
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 = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
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)
}
fun iterateSplitWords() {
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
$loopLabel sty $indexVar
lda ${iterableName}_lsb,y
sta $loopvarName
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
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 indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
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)
}
fun iterateWords(actuallyLongs: Boolean = false) {
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
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
lda $iterableName+2,y
sta $loopvarName+2
lda $iterableName+3,y
sta $loopvarName+3""")
}
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
cpy #${numElements*4u}
beq $endLabel
bne $loopLabel""")
} else {
asmgen.out("""
ldy $indexVar
iny
@@ -643,6 +664,19 @@ $loopLabel sty $indexVar
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
}
} else {
if(actuallyLongs) {
// array size is 64 longs, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
bne $loopLabel
beq $endLabel""")
} else {
// array size is 128 words, 256 bytes
asmgen.out("""
@@ -652,23 +686,29 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
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")
}
}
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.isFloatArray -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
asmgen.out(endLabel)
}
when {
iterableDt.isString -> iterateStrings()
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
iterableDt.isSplitWordArray -> iterateSplitWords()
iterableDt.isWordArray -> iterateWords()
iterableDt.isLongArray -> iterateWords(true)
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.removeLast()
@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we consider them NOT to be optimized into (possibly different) CPU registers.
// Just load them in whatever the register spec says.
return when (params.size) {
1 -> params[0].register == null && (params[0].type.isIntegerOrBool || params[0].type.isPointer)
1 -> params[0].register == null && (params[0].type.isWordOrByteOrBool || params[0].type.isPointer)
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
else -> false
}
@@ -206,8 +206,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
if (arg.name == "mkword" || arg.name == "mklong2")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
if (arg.name == "mklong")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
return !arg.isSimple()
}
is PtAddressOf -> false
@@ -46,6 +46,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
return when {
rightDt.isByteOrBool -> translateIfByte(stmt, jumpAfterIf)
rightDt.isWord || rightDt.isPointer -> translateIfWord(stmt, compareCond, jumpAfterIf)
rightDt.isLong -> translateIfLong(stmt, compareCond, jumpAfterIf)
rightDt.isFloat -> translateIfFloat(stmt, compareCond, jumpAfterIf)
else -> throw AssemblyError("weird dt")
}
@@ -575,6 +576,32 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateIfLong(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val constValue = condition.right.asConstInteger()
if(constValue==0) {
// optimized comparisons with zero
return when (condition.operator) {
"==" -> longEqualsZero(condition.left, false, jumpAfterIf, stmt)
"!=" -> longEqualsZero(condition.left, true, jumpAfterIf, stmt)
"<" -> longLessZero(condition.left, false, jumpAfterIf, stmt)
"<=" -> longLessZero(condition.left, true, jumpAfterIf, stmt)
">" -> longGreaterZero(condition.left, false, jumpAfterIf, stmt)
">=" -> longGreaterZero(condition.left, true, jumpAfterIf, stmt)
else -> throw AssemblyError("expected comparison operator")
}
}
return when (condition.operator) {
"==" -> longEqualsValue(condition.left, condition.right, false, jumpAfterIf, stmt)
"!=" -> longEqualsValue(condition.left, condition.right, true, jumpAfterIf, stmt)
"<" -> TODO("long < 0")
"<=" -> TODO("long <= 0")
">" -> TODO("long > 0")
">=" -> TODO("long >= 0")
else -> throw AssemblyError("expected comparison operator")
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()
@@ -606,7 +633,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordGreaterEqualsZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word>=0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
@@ -619,7 +646,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun wordLessZero(value: PtExpression, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for word<0
if(signed) {
loadAndCmp0MSB(value)
loadAndCmp0MSB(value, false)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
@@ -894,23 +921,31 @@ _jump jmp (${target.asmLabel})
}
private fun loadAndCmp0MSB(value: PtExpression) {
private fun loadAndCmp0MSB(value: PtExpression, long: Boolean) {
when(value) {
is PtArrayIndexer -> {
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)
if(value.splitWords) {
require(!long)
asmgen.out(" lda ${varname}_msb,y")
}
else if(long)
asmgen.out(" lda $varname+3,y")
else
asmgen.out(" lda $varname+1,y")
}
is PtIdentifier -> {
val varname = asmgen.asmVariableName(value)
asmgen.out(" lda $varname+1")
if(long)
asmgen.out(" lda $varname+3")
else
asmgen.out(" lda $varname+1")
}
is PtAddressOf -> {
require(!long)
if(value.isFromArrayElement) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
@@ -923,6 +958,7 @@ _jump jmp (${target.asmLabel})
}
}
else -> {
require(!long)
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
}
@@ -1226,6 +1262,50 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsZero(value: PtExpression, notEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3""")
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun longLessZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long <= 0")
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bmi", "bpl", jump, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
}
}
private fun longGreaterZero(value: PtExpression, lessEquals: Boolean, jump: PtJump?, stmt: PtIfElse) {
if(lessEquals) {
TODO("long >= 0")
} else {
loadAndCmp0MSB(value, true)
if (jump != null)
translateJumpElseBodies("bpl", "bmi", jump, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
}
}
private fun wordEqualsZero(value: PtExpression, notEquals: Boolean, signed: Boolean, jump: PtJump?, stmt: PtIfElse) {
// special case for (u)word == 0
@@ -1303,6 +1383,93 @@ _jump jmp (${target.asmLabel})
}
}
private fun longEqualsValue(
left: PtExpression,
right: PtExpression,
notEquals: Boolean,
jump: PtJump?,
stmt: PtIfElse
) {
// TODO this can be optimized somewhat more when the left operand is a variable as well
// we only optimize for a const right value for now
val constRight = right.asConstInteger()
val variableRight = (right as? PtIdentifier)?.name
if(constRight!=null) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
val hex = constRight.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda cx16.r0
cmp #$${hex.substring(6,8)}
bne +
lda cx16.r0+1
cmp #$${hex.substring(4, 6)}
bne +
lda cx16.r0+2
cmp #$${hex.substring(2, 4)}
bne +
lda cx16.r0+3
cmp #$${hex.take(2)}
+""")
} else if(variableRight!=null) {
require(right.type.isLong)
val variableLeft = (left as? PtIdentifier)?.name
if(variableLeft!=null) {
asmgen.out("""
lda #<$variableLeft
ldy #>$variableLeft
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$variableRight
ldy #>$variableRight
jsr prog8_lib.long_not_equals""")
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R0R1_32, left.type.isSigned)
asmgen.out("""
lda cx16.r0
cmp $variableRight
bne +
lda cx16.r0+1
cmp $variableRight+1
bne +
lda cx16.r0+2
cmp $variableRight+2
bne +
lda cx16.r0+3
cmp $variableRight+3
+""")
}
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.R2R3_32, left.type.isSigned)
asmgen.assignExpressionToRegister(right, RegisterOrPair.R0R1_32, right.type.isSigned)
asmgen.out("""
lda cx16.r0
cmp cx16.r2
bne +
lda cx16.r0+1
cmp cx16.r2+1
bne +
lda cx16.r0+2
cmp cx16.r2+2
bne +
lda cx16.r0+3
cmp cx16.r2+3
+""")
}
if(notEquals) {
if (jump != null)
translateJumpElseBodies("bne", "beq", jump, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else {
if (jump != null)
translateJumpElseBodies("beq", "bne", jump, stmt.elseScope)
else
translateIfElseBodies("bne", stmt)
}
}
private fun wordEqualsValue(
left: PtExpression,
right: PtExpression,
@@ -32,6 +32,14 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
expr.type.isLong -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.R0R1_32, true)
asmgen.jmp(endLabel)
asmgen.out(falseLabel)
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.R0R1_32, true)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R0R1_32)
}
expr.type.isFloat -> {
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
@@ -114,6 +122,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
return when {
rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel)
rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel)
rightDt.isLong -> translateIfExpressionLongConditionBranch(condition, falseLabel)
rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel)
else -> throw AssemblyError("weird dt")
}
@@ -200,6 +209,36 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionLongConditionBranch(condition: PtBinaryExpression, falseLabel: String) {
// TODO can we reuse this whole thing from IfElse ?
val constValue = condition.right.asConstInteger()
if(constValue!=null) {
if (constValue == 0) {
when (condition.operator) {
"==" -> return translateLongExprIsZero(condition.left, falseLabel)
"!=" -> return translateLongExprIsNotZero(condition.left, falseLabel)
}
}
if (constValue != 0) {
when (condition.operator) {
"==" -> return translateLongExprEqualsNumber(condition.left, constValue, falseLabel)
"!=" -> return translateLongExprNotEqualsNumber(condition.left, constValue, falseLabel)
}
}
}
val variable = condition.right as? PtIdentifier
if(variable!=null) {
when (condition.operator) {
"==" -> return translateLongExprEqualsVariable(condition.left, variable, falseLabel)
"!=" -> return translateLongExprNotEqualsVariable(condition.left, variable, falseLabel)
}
}
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
asmgen.assignConditionValueToRegisterAndTest(condition)
asmgen.out(" beq $falseLabel")
}
private fun translateIfExpressionFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) {
val constValue = (condition.right as? PtNumber)?.number
if(constValue==0.0) {
@@ -339,6 +378,89 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
// if L==number
// TODO reuse code from ifElse?
val hex = number.toUInt().toString(16).padStart(8, '0')
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp #${hex.substring(6, 8)}
bne $falseLabel
lda $varname+1
cmp #${hex.substring(4, 6)}
bne $falseLabel
lda $varname+2
cmp #${hex.substring(2, 4)}
bne $falseLabel
lda $varname+3
cmp #${hex.take(2)}
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
cmp #${hex.substring(6, 8)}
bne $falseLabel
lda cx16.r0+1
cmp #${hex.substring(4, 6)}
bne $falseLabel
lda cx16.r0+2
cmp #${hex.substring(2, 4)}
bne $falseLabel
lda cx16.r0+3
cmp #${hex.take(2)}
bne $falseLabel""")
}
}
private fun translateLongExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L==variable
// TODO reuse code from ifElse?
val varname2 = asmgen.asmVariableName(variable)
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
cmp $varname2
bne $falseLabel
lda $varname+1
cmp $varname2+1
bne $falseLabel
lda $varname+2
cmp $varname2+2
bne $falseLabel
lda $varname+3
cmp $varname2+3
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
cmp $varname2
bne $falseLabel
lda cx16.r0+1
cmp $varname2+1
bne $falseLabel
lda cx16.r0+2
cmp $varname2+2
bne $falseLabel
lda cx16.r0+3
cmp $varname2+3
bne $falseLabel""")
}
}
private fun translateLongExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) {
TODO("if expression LONG != number ${expr.position}")
}
private fun translateLongExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) {
// if L!=variable
TODO("if expression LONG != variable ${expr.position}")
}
private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if w!=0
// TODO reuse code from ifElse?
@@ -369,6 +491,50 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
private fun translateLongExprIsZero(expr: PtExpression, falseLabel: String) {
// if L==0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
bne $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3
bne $falseLabel""")
}
}
private fun translateLongExprIsNotZero(expr: PtExpression, falseLabel: String) {
// if L!=0
// TODO reuse code from ifElse?
if(expr is PtIdentifier) {
val varname = asmgen.asmVariableName(expr)
asmgen.out("""
lda $varname
ora $varname+1
ora $varname+2
ora $varname+3
beq $falseLabel""")
} else {
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R0R1_32)
asmgen.out("""
lda cx16.r0
ora cx16.r0+1
ora cx16.r0+2
ora cx16.r0+3
beq $falseLabel""")
}
}
private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) {
// optimized code for byte comparisons with 0
@@ -210,7 +210,7 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("prog8_slabs\t.block")
asmgen.out("$StMemorySlabBlockName\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
@@ -377,6 +377,7 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
@@ -418,14 +419,7 @@ internal class ProgramAndVarsGen(
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")
}
val type = asmTypeString(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")
@@ -434,6 +428,7 @@ internal class ProgramAndVarsGen(
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")
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
@@ -445,11 +440,17 @@ internal class ProgramAndVarsGen(
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .endblock\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("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
}
@@ -564,6 +565,7 @@ internal class ProgramAndVarsGen(
else when(dt) {
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.LONG -> asmgen.out("$name .sint ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -774,6 +776,7 @@ 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.isLong -> asmgen.out("${variable.name}\t.dint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
alignVar(variable.align)
@@ -822,6 +825,7 @@ internal class ProgramAndVarsGen(
// 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.isLong -> asmgen.out("${variable.name}\t.dint $initialValue")
// dt.isFloat -> {
// if(initialValue==0) {
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
@@ -866,7 +870,7 @@ internal class ProgramAndVarsGen(
}
}
dt.isSplitWordArray -> {
if(dt.elementType().isUnsignedWord) {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
@@ -898,6 +902,16 @@ internal class ProgramAndVarsGen(
asmgen.out(" .sint " + chunk.joinToString())
}
}
dt.isLongArray -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.dint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .dint " + chunk.joinToString())
}
}
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
@@ -914,7 +928,7 @@ internal class ProgramAndVarsGen(
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null))
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
}
@@ -972,7 +986,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
@@ -984,8 +998,15 @@ internal class ProgramAndVarsGen(
else
asmgen.asmSymbolName(addrOfSymbol)
}
else
else if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
}
}
else -> throw AssemblyError("invalid dt")
}
@@ -1013,7 +1034,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isArray && dt.elementType().isSignedWord -> array.map {
dt.isSignedWordArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@@ -1021,6 +1042,14 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
dt.isLongArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(8, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -42,6 +42,12 @@ internal class AnyExprAsmGen(
}
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
expr.type.isLong -> {
require(expr.left.type.isLong && expr.right.type.isLong) {
"both operands must be longs"
}
throw AssemblyError("expression should have been handled otherwise: long ${expr.operator} at ${expr.position}")
}
expr.type.isFloat -> {
require(expr.left.type.isFloat && expr.right.type.isFloat) {
"both operands must be floats"
@@ -10,8 +10,8 @@ internal enum class TargetStorageKind {
ARRAY,
MEMORY,
REGISTER,
POINTER, // wherever the pointer variable points to
VOID // assign nothing - used in multi-value assigns for void placeholders
POINTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
}
internal enum class SourceStorageKind {
@@ -114,25 +114,17 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.FAC2 -> {
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
}
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
in Cx16VirtualRegisters -> {
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
in combinedLongRegisters -> {
val dt = if(signed) DataType.LONG
else
TODO("unsigned long")
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
else -> throw AssemblyError("weird register $registers")
}
}
@@ -5,6 +5,7 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
import kotlin.math.log2
internal class AssignmentAsmGen(
@@ -209,6 +210,7 @@ internal class AssignmentAsmGen(
when (assign.target.datatype.base) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num)
BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num)
BaseDataType.LONG -> assignConstantLong(assign.target, num)
BaseDataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
else -> throw AssemblyError("weird numval type")
}
@@ -221,6 +223,7 @@ internal class AssignmentAsmGen(
BaseDataType.BOOL -> assignConstantByte(assign.target, if(num==0.0) 0 else 1)
BaseDataType.UBYTE, BaseDataType.BYTE -> assignConstantByte(assign.target, num.toInt())
BaseDataType.UWORD, BaseDataType.WORD -> assignConstantWord(assign.target, num.toInt())
BaseDataType.LONG -> assignConstantLong(assign.target, num.toInt())
BaseDataType.FLOAT -> assignConstantFloat(assign.target, num)
BaseDataType.POINTER -> assignConstantWord(assign.target, num.toInt())
else -> throw AssemblyError("weird numval type")
@@ -243,6 +246,7 @@ internal class AssignmentAsmGen(
else
assignVariableWord(assign.target, variable, assign.source.datatype)
}
targetDt.isLong -> assignVariableLong(assign.target, variable, assign.source.datatype)
targetDt.isFloat -> assignVariableFloat(assign.target, variable)
targetDt.isString -> assignVariableString(assign.target, variable)
targetDt.isPointer -> assignVariableWord(assign.target, variable, assign.source.datatype)
@@ -392,7 +396,7 @@ internal class AssignmentAsmGen(
// fallback assignmen through temporary pointer var
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A, false, true)
}
@@ -535,7 +539,7 @@ internal class AssignmentAsmGen(
private fun assignPrefixExpr(assign: AsmAssignment, value: PtPrefix, scope: IPtSubroutine?) {
if(assign.target.array==null) {
if(assign.source.datatype isAssignableTo assign.target.datatype || (assign.source.datatype.isBool && assign.target.datatype.isByte)) {
if(assign.source.datatype.isIntegerOrBool) {
if(assign.source.datatype.isWordOrByteOrBool) {
val signed = assign.source.datatype.isSigned
if(assign.source.datatype.isByteOrBool) {
assignExpressionToRegister(value.value, RegisterOrPair.A, signed)
@@ -650,6 +654,10 @@ internal class AssignmentAsmGen(
else -> throw AssemblyError("str return value type mismatch with target")
}
}
returnDt== BaseDataType.LONG -> {
// longs are in R0:R1 (r0=lsw, r1=msw)
assignRegisterLong(target, RegisterOrPair.R0R1_32)
}
returnDt==BaseDataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(target)
@@ -702,22 +710,8 @@ internal class AssignmentAsmGen(
RegisterOrPair.AX -> assignVirtualRegister(target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignVirtualRegister(target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignVirtualRegister(target, RegisterOrPair.XY)
RegisterOrPair.R0 -> assignVirtualRegister(target, RegisterOrPair.R0)
RegisterOrPair.R1 -> assignVirtualRegister(target, RegisterOrPair.R1)
RegisterOrPair.R2 -> assignVirtualRegister(target, RegisterOrPair.R2)
RegisterOrPair.R3 -> assignVirtualRegister(target, RegisterOrPair.R3)
RegisterOrPair.R4 -> assignVirtualRegister(target, RegisterOrPair.R4)
RegisterOrPair.R5 -> assignVirtualRegister(target, RegisterOrPair.R5)
RegisterOrPair.R6 -> assignVirtualRegister(target, RegisterOrPair.R6)
RegisterOrPair.R7 -> assignVirtualRegister(target, RegisterOrPair.R7)
RegisterOrPair.R8 -> assignVirtualRegister(target, RegisterOrPair.R8)
RegisterOrPair.R9 -> assignVirtualRegister(target, RegisterOrPair.R9)
RegisterOrPair.R10 -> assignVirtualRegister(target, RegisterOrPair.R10)
RegisterOrPair.R11 -> assignVirtualRegister(target, RegisterOrPair.R11)
RegisterOrPair.R12 -> assignVirtualRegister(target, RegisterOrPair.R12)
RegisterOrPair.R13 -> assignVirtualRegister(target, RegisterOrPair.R13)
RegisterOrPair.R14 -> assignVirtualRegister(target, RegisterOrPair.R14)
RegisterOrPair.R15 -> assignVirtualRegister(target, RegisterOrPair.R15)
in Cx16VirtualRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
in combinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
else -> {
val sflag = returnValue.first.statusflag
if(sflag!=null)
@@ -770,6 +764,10 @@ internal class AssignmentAsmGen(
assignRegisterByte(target, CpuRegister.A, false, false)
}
target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
target.datatype.isLong -> {
require(register in combinedLongRegisters)
assignRegisterLong(target, register)
}
else -> throw AssemblyError("expected byte or word")
}
}
@@ -1124,7 +1122,7 @@ internal class AssignmentAsmGen(
asmgen.out(" pla")
asmgen.out(" sta cx16.r0 | sty cx16.r0+1")
}
asmgen.out(" jsr verafx.muls")
asmgen.out(" jsr verafx.muls16")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} else {
@@ -1145,6 +1143,14 @@ internal class AssignmentAsmGen(
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type.isSigned)
if (value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr prog8_math.mul_byte_${value}")
else if(value in powersOfTwoInt) {
val shifts = log2(value.toDouble()).toInt()
if(shifts>=8) {
asmgen.out(" lda #0")
} else {
repeat(shifts) { asmgen.out(" asl a") }
}
}
else
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false, true)
@@ -1155,14 +1161,33 @@ internal class AssignmentAsmGen(
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" jsr prog8_math.mul_word_${value}")
}
else if(value in powersOfTwoInt) {
val shifts = log2(value.toDouble()).toInt()
if(shifts>=16) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" lda #0 | ldy #0")
} else {
if(target.kind==TargetStorageKind.VARIABLE && target.datatype.isWord) {
assignExpressionToVariable(expr.left, target.asmVarname, target.datatype)
repeat(shifts) { asmgen.out(" asl ${target.asmVarname} | rol ${target.asmVarname}+1") }
return true
} else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
asmgen.out(" sty P8ZP_SCRATCH_REG")
repeat(shifts) { asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") }
asmgen.out(" ldy P8ZP_SCRATCH_REG")
}
}
}
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
if(expr.definingBlock()!!.options.veraFxMuls){
// cx16 verafx hardware mul
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")
asmgen.out("""
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls""")
jsr verafx.muls16""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "prog8_math.multiply_words.multiplier")
asmgen.out(" jsr prog8_math.multiply_words")
@@ -1209,7 +1234,7 @@ internal class AssignmentAsmGen(
asmgen.out(" jsr prog8_math.asl_byte_A")
assignRegisterByte(target, CpuRegister.A, signed, true)
return true
} else {
} else if(dt.isWord) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
asmgen.restoreRegisterStack(CpuRegister.X, true)
if(expr.operator==">>")
@@ -1221,6 +1246,27 @@ internal class AssignmentAsmGen(
asmgen.out(" jsr prog8_math.asl_word_AY")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} else if(dt.isLong) {
if(target.kind==TargetStorageKind.VARIABLE) {
asmgen.out(" pla")
asmgen.assignExpressionTo(expr.left, target)
require(expr.right.type.isByte)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.X)
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname}")
if (expr.operator == "<<") {
asmgen.out(" jsr prog8_lib.long_shiftleftX_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_shiftrightX_inplace")
}
return true
}
assignExpressionToRegister(expr.left, RegisterOrPair.R0R1_32, signed)
asmgen.out(" pla | sta P8ZP_SCRATCH_REG")
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r0", expr.operator, "P8ZP_SCRATCH_REG")
assignRegisterLong(target, RegisterOrPair.R0R1_32)
return true
}
}
else {
@@ -1340,12 +1386,32 @@ internal class AssignmentAsmGen(
return true
}
}
} else if(dt.isLong) {
if(target.kind==TargetStorageKind.VARIABLE) {
asmgen.assignExpressionTo(expr.left, target)
require(expr.right.type.isByte)
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname}")
asmgen.out(" ldx #$shifts")
if (expr.operator == "<<") {
asmgen.out(" jsr prog8_lib.long_shiftleftX_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_shiftrightX_inplace")
}
return true
}
assignExpressionToRegister(expr.left, RegisterOrPair.R0R1_32, signed)
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r0", expr.operator, shifts)
assignRegisterLong(target, RegisterOrPair.R0R1_32)
return true
}
}
return false
}
private fun optimizedPlusMinExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
// TODO can this use the target directly as intermediate, when it's a variable? To save the final assignRegister call.
val dt = expr.type
val left = expr.left
val right = expr.right
@@ -1660,9 +1726,87 @@ internal class AssignmentAsmGen(
}
}
}
else if(dt.isLong) {
return optimizedPlusMinLongExpr(expr, target)
}
return false
}
private fun optimizedPlusMinLongExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
val left = expr.left
val right = expr.right
when(right) {
is PtIdentifier -> {
if(target.kind == TargetStorageKind.VARIABLE) {
asmgen.assignExpressionTo(left, target)
val rightsym = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightsym
ldy #>$rightsym
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}""")
if (expr.operator == "+") {
asmgen.out(" jsr prog8_lib.long_add_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_sub_inplace")
}
return true
} else {
TODO("add/subtract long ${target.position} - use simple expressions and temporary variables for now")
}
}
is PtNumber -> {
val hex = right.number.toInt().toString(16).padStart(8, '0')
if (target.kind == TargetStorageKind.VARIABLE) {
asmgen.assignExpressionTo(left, target)
if (expr.operator == "+") {
asmgen.out("""
lda ${target.asmVarname}
clc
adc #$${hex.substring(6,8)}
sta ${target.asmVarname}
lda ${target.asmVarname}+1
adc #$${hex.substring(4, 6)}
sta ${target.asmVarname}+1
lda ${target.asmVarname}+2
adc #$${hex.substring(2, 4)}
sta ${target.asmVarname}+2
lda ${target.asmVarname}+3
adc #$${hex.take(2)}
sta ${target.asmVarname}+3""")
} else {
asmgen.out("""
lda ${target.asmVarname}
sec
sbc #$${hex.substring(6, 8)}
sta ${target.asmVarname}
lda ${target.asmVarname}+1
sbc #$${hex.substring(4, 6)}
sta ${target.asmVarname}+1
lda ${target.asmVarname}+2
sbc #$${hex.substring(2, 4)}
sta ${target.asmVarname}+2
lda ${target.asmVarname}+3
sbc #$${hex.take(2)}
sta ${target.asmVarname}+3""")
}
return true
} else {
TODO("add/subtract long ${target.position} - use simple expressions and temporary variables for now")
}
}
else -> {
assignExpressionToRegister(expr.left, RegisterOrPair.R2R3_32, expr.left.type.isSigned)
assignExpressionToRegister(expr.right, RegisterOrPair.R0R1_32, expr.right.type.isSigned)
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r2", expr.operator, "cx16.r0")
assignRegisterLong(target, RegisterOrPair.R2R3_32)
return true
}
}
}
private fun optimizedPointerIndexPlusMinusByteIntoA(value: PtExpression, operator: String, mem: PtMemoryByte): Boolean {
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
val address = mem.address as? PtBinaryExpression
@@ -1783,6 +1927,58 @@ internal class AssignmentAsmGen(
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
else if (expr.left.type.isLong && expr.right.type.isLong) {
if(target.kind == TargetStorageKind.VARIABLE) {
if(expr.left is PtIdentifier) {
asmgen.assignExpressionTo(expr.right, target)
val varname = asmgen.asmVariableName(expr.left as PtIdentifier)
asmgen.out("""
lda #<$varname
ldy #>$varname
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}""")
when(expr.operator) {
"|" -> asmgen.out(" jsr prog8_lib.long_or_inplace")
"&" -> asmgen.out(" jsr prog8_lib.long_and_inplace")
"^" -> asmgen.out(" jsr prog8_lib.long_xor_inplace")
else -> throw AssemblyError("wrong bitwise operator")
}
return true
} else if(expr.right is PtIdentifier) {
asmgen.assignExpressionTo(expr.left, target)
val varname = asmgen.asmVariableName(expr.right as PtIdentifier)
asmgen.out("""
lda #<$varname
ldy #>$varname
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}""")
when(expr.operator) {
"|" -> asmgen.out(" jsr prog8_lib.long_or_inplace")
"&" -> asmgen.out(" jsr prog8_lib.long_and_inplace")
"^" -> asmgen.out(" jsr prog8_lib.long_xor_inplace")
else -> throw AssemblyError("wrong bitwise operator")
}
return true
}
}
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R2R3_32, expr.left.type.isSigned)
val constval = expr.right.asConstInteger()
val varname = (expr.right as? PtIdentifier)?.name
if(constval!=null)
augmentableAsmGen.inplacemodificationLongWithLiteralval("cx16.r2", expr.operator, constval)
else if(varname!=null)
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r2", expr.operator, varname)
else
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r2", expr.operator, expr.right)
assignRegisterLong(target, RegisterOrPair.R2R3_32)
return true
}
return false
}
@@ -2217,7 +2413,7 @@ $endLabel""")
fun assignViaExprEval(addressExpression: PtExpression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
@@ -2292,6 +2488,9 @@ $endLabel""")
assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base)
}
valueDt.isLong -> {
TODO("assign typecasted long to $targetDt ${value.position}")
}
valueDt.isWord || valueDt.isPointer -> {
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base)
@@ -2389,9 +2588,9 @@ $endLabel""")
}
}
if(targetDt.isInteger && valueDt.isIntegerOrBool && valueDt.isAssignableTo(targetDt)) {
require(targetDt.isWord && valueDt.isByteOrBool) {
"should be byte to word assignment ${origTypeCastExpression.position}"
if(targetDt.isInteger && valueDt.isByteOrBool && valueDt.isAssignableTo(targetDt)) {
require(targetDt.isWord || targetDt.isLong) {
"should be byte to word or long assignment ${origTypeCastExpression.position}"
}
when(target.kind) {
// TargetStorageKind.VARIABLE -> {
@@ -2412,7 +2611,7 @@ $endLabel""")
return
}
TargetStorageKind.REGISTER -> {
// byte to word, just assign to registers
// byte to word or long, just assign to registers
assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
return
}
@@ -2526,6 +2725,7 @@ $endLabel""")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> TODO("assign typecasted to LONG")
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
@@ -2553,6 +2753,11 @@ $endLabel""")
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
}
BaseDataType.LONG -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
@@ -2581,6 +2786,7 @@ $endLabel""")
BaseDataType.WORD -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> TODO("assign typecasted to LONG")
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
@@ -2610,6 +2816,14 @@ $endLabel""")
BaseDataType.UWORD, BaseDataType.POINTER -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> {
asmgen.out("""
lda $sourceAsmVarName
sta $targetAsmVarName
lda $sourceAsmVarName+1
sta $targetAsmVarName+1""")
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
@@ -2670,6 +2884,7 @@ $endLabel""")
else
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> TODO("assign typecasted to LONG")
BaseDataType.FLOAT -> {
when(regs) {
RegisterOrPair.A -> asmgen.out(" tay")
@@ -2709,6 +2924,7 @@ $endLabel""")
asmgen.signExtendAYlsb(sourceDt)
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
}
BaseDataType.LONG -> TODO("assign typecasted to LONG")
BaseDataType.FLOAT -> {
when(regs) {
RegisterOrPair.A -> {}
@@ -2740,6 +2956,7 @@ $endLabel""")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.LONG -> TODO("assign typecasted to LONG")
BaseDataType.FLOAT -> {
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
@@ -2769,6 +2986,15 @@ $endLabel""")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.LONG -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
@@ -2975,6 +3201,112 @@ $endLabel""")
}
}
private fun assignVariableLong(target: AsmAssignTarget, varName: String, sourceDt: DataType) {
require(sourceDt.isByte || sourceDt.isWord || sourceDt.isLong) {
"need byte/word/long as source value to assign to long variable $varName ${target.position}"
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
when(sourceDt) {
DataType.BYTE -> {
TODO("signed byte to long var")
}
DataType.UBYTE -> {
TODO("ubyte to long var")
}
DataType.WORD -> {
TODO("signed word to long var")
}
DataType.UWORD -> {
TODO("ubyte to long var")
}
DataType.LONG -> {
asmgen.out("""
lda $varName
sta ${target.asmVarname}
lda $varName+1
sta ${target.asmVarname}+1
lda $varName+2
sta ${target.asmVarname}+2
lda $varName+3
sta ${target.asmVarname}+3""")
}
else -> throw AssemblyError("wrong dt ${target.position}")
}
}
TargetStorageKind.ARRAY -> {
require(sourceDt.isLong)
val deref = target.array!!.pointerderef
if(deref!=null) {
pointergen.assignLongVar(IndexedPtrTarget(target), varName)
return
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
lda $varName
sta ${target.asmVarname},y
lda $varName+1
sta ${target.asmVarname}+1,y
lda $varName+2
sta ${target.asmVarname}+2,y
lda $varName+3
sta ${target.asmVarname}+3,y""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in combinedLongRegisters)
val regstart = target.register.toString().take(2).lowercase()
when(sourceDt) {
DataType.BYTE -> {
asmgen.out(" lda $varName | sta cx16.$regstart")
asmgen.signExtendLongVariable("cx16.$regstart", sourceDt.base)
}
DataType.UBYTE -> {
asmgen.out("""
lda $varName
sta cx16.$regstart
lda #0
sta cx16.$regstart+1
sta cx16.$regstart+2
sta cx16.$regstart+3""")
}
DataType.WORD -> {
asmgen.out("""
lda $varName
sta cx16.$regstart
lda $varName+1
sta cx16.$regstart+1""")
asmgen.signExtendLongVariable("cx16.$regstart", sourceDt.base)
}
DataType.UWORD -> {
asmgen.out("""
lda $varName
sta cx16.$regstart
lda $varName+1
sta cx16.$regstart+1
lda #0
sta cx16.$regstart+2
sta cx16.$regstart+3""")
}
DataType.LONG -> {
asmgen.out("""
lda $varName
sta cx16.$regstart
lda $varName+1
sta cx16.$regstart+1
lda $varName+2
sta cx16.$regstart+2
lda $varName+3
sta cx16.$regstart+3""")
}
else -> throw AssemblyError("wrong dt ${target.position}")
}
}
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
private fun assignVariableWord(target: AsmAssignTarget, varName: String, sourceDt: DataType) {
if(sourceDt.isSignedByte) {
// need to sign extend
@@ -3454,6 +3786,49 @@ $endLabel""")
}
}
internal fun assignRegisterLong(target: AsmAssignTarget, pairedRegisters: RegisterOrPair) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(pairedRegisters in combinedLongRegisters) {
val startreg = pairedRegisters.startregname()
asmgen.out("""
lda cx16.$startreg
sta ${target.asmVarname}
lda cx16.$startreg+1
sta ${target.asmVarname}+1
lda cx16.$startreg+2
sta ${target.asmVarname}+2
lda cx16.$startreg+3
sta ${target.asmVarname}+3""")
}
else throw AssemblyError("only combined vreg allowed as long target ${target.position}")
}
TargetStorageKind.ARRAY -> {
TODO("assign 32 bits int in R0:R1 into array ${target.position}")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
val targetreg = target.register!!
require(targetreg in combinedLongRegisters && pairedRegisters in combinedLongRegisters)
if(targetreg!=pairedRegisters) {
val sourceStartReg = pairedRegisters.startregname()
val targetStartReg = targetreg.startregname()
asmgen.out("""
lda cx16.$sourceStartReg
sta cx16.$targetStartReg
lda cx16.$sourceStartReg+1
sta cx16.$targetStartReg+1
lda cx16.$sourceStartReg+2
sta cx16.$targetStartReg+2
lda cx16.$sourceStartReg+3
sta cx16.$targetStartReg+3""")
}
}
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean, extendWord: Boolean) {
val assignAsWord = target.datatype.isWord
@@ -3892,7 +4267,7 @@ $endLabel""")
}
}
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register ${target.position}")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
@@ -3901,6 +4276,92 @@ $endLabel""")
}
}
private fun assignConstantLong(target: AsmAssignTarget, long: Int) {
if(long==0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
// optimize setting zero value for this processor
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
stz ${target.asmVarname}
stz ${target.asmVarname}+1
stz ${target.asmVarname}+2
stz ${target.asmVarname}+3""")
}
TargetStorageKind.ARRAY -> {
val deref = target.array!!.pointerderef
if(deref!=null) {
pointergen.assignLong(IndexedPtrTarget(target), 0)
return
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
lda #0
sta ${target.asmVarname},y
sta ${target.asmVarname}+1,y
sta ${target.asmVarname}+2,y
sta ${target.asmVarname}+3,y""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
return
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
fun store(hexbyte: String, offset: Int) {
if(asmgen.isTargetCpu(CpuType.CPU65C02) && hexbyte=="00") {
asmgen.out(" stz ${target.asmVarname}+$offset")
} else {
asmgen.out(" lda #$$hexbyte | sta ${target.asmVarname}+$offset")
}
}
val hex = long.toUInt().toString(16).padStart(8, '0')
store(hex.substring(6,8), 0)
store(hex.substring(4,6), 1)
store(hex.substring(2,4), 2)
store(hex.substring(0,2), 3)
}
TargetStorageKind.ARRAY -> {
val deref = target.array!!.pointerderef
if(deref!=null) {
pointergen.assignLong(IndexedPtrTarget(target), long)
return
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
val hex = long.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda #$${hex.substring(6,8)}
sta ${target.asmVarname},y
lda #$${hex.substring(4, 6)}
sta ${target.asmVarname}+1,y
lda #$${hex.substring(2, 4)}
sta ${target.asmVarname}+2,y
lda #$${hex.take(2)}
sta ${target.asmVarname}+3,y""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> {
require(target.register in combinedLongRegisters)
val regstart = target.register!!.startregname()
val hex = long.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda #$${hex.substring(6,8)}
sta cx16.$regstart
lda #$${hex.substring(4,6)}
sta cx16.$regstart+1
lda #$${hex.substring(2,4)}
sta cx16.$regstart+2
lda #$${hex.take(2)}
sta cx16.$regstart+3""")
}
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
if(word==0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
// optimize setting zero value for this processor
@@ -3914,7 +4375,7 @@ $endLabel""")
TargetStorageKind.ARRAY -> {
val deref = target.array!!.pointerderef
if(deref!=null) {
pointergen.assignWord(IndexedPtrTarget(target), word)
pointergen.assignWord(IndexedPtrTarget(target), 0)
return
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
@@ -4490,6 +4951,26 @@ $endLabel""")
else -> throw AssemblyError("weird target")
}
}
targetDt.isLong -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${target.asmVarname}
eor #255
sta ${target.asmVarname}
lda ${target.asmVarname}+1
eor #255
sta ${target.asmVarname}+1
lda ${target.asmVarname}+2
eor #255
sta ${target.asmVarname}+2
lda ${target.asmVarname}+3
eor #255
sta ${target.asmVarname}+3""")
}
else -> TODO("LONG INVERT ${target.kind} ${target.position}")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
@@ -4599,6 +5080,31 @@ $endLabel""")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
datatype.isLong -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}
lda #0
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1
lda #0
sbc ${target.asmVarname}+2
sta ${target.asmVarname}+2
lda #0
sbc ${target.asmVarname}+3
sta ${target.asmVarname}+3""")
}
TargetStorageKind.ARRAY -> TODO(" - long array ${target.position}")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
TargetStorageKind.REGISTER -> TODO("32 bits register assign? (we have no 32 bits registers right now) ${target.position}")
TargetStorageKind.POINTER -> throw AssemblyError("can't assign long to pointer, pointers are 16 bits ${target.position}")
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
datatype.isFloat -> {
when (target.kind) {
TargetStorageKind.REGISTER -> {
@@ -106,6 +106,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(target.asmVarname, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(target.asmVarname, operator, value.asmVarname)
SourceStorageKind.EXPRESSION -> inplacemodificationLongWithExpression(target.asmVarname, operator, value.expression!!)
SourceStorageKind.REGISTER -> TODO("32 bits register inplace modification? ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace modify long with array ${target.position}")
SourceStorageKind.MEMORY -> TODO("memread into long ${target.position}")
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(target.asmVarname, operator, value.boolean!!.asInt().toDouble())
@@ -291,6 +302,25 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
target.datatype.isLong -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.boolean!!.asInt())
SourceStorageKind.LITERALNUMBER -> inplacemodificationLongWithLiteralval(targetVarName, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationLongWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("inplace long modifiication ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modifiication ${target.position}")
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
TODO("inplace long modifiication ${target.position}")
} else {
TODO("inplace long modifiication ${target.position}")
}
}
}
}
target.datatype.isFloat -> {
when(value.kind) {
SourceStorageKind.LITERALBOOLEAN -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.boolean!!.asInt().toDouble())
@@ -523,6 +553,448 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0R1_32, value.type.isSigned)
inplacemodificationLongWithVariable(targetVar, operator, "cx16.r0")
}
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
when(operator) {
"+" -> {
asmgen.out("""
clc
lda $targetVar
adc $sourceVar
sta $targetVar
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3""")
}
"-" -> {
asmgen.out("""
sec
lda $targetVar
sbc $sourceVar
sta $targetVar
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3""")
}
"<<" -> {
asmgen.out("""
ldy $sourceVar
- asl $targetVar
rol $targetVar+1
rol $targetVar+2
rol $targetVar+3
dey
bne -""")
}
">>" -> {
asmgen.out("""
ldy $sourceVar
- lda $targetVar+3
asl a ; save sign bit
ror $targetVar+3
ror $targetVar+2
ror $targetVar+1
ror $targetVar
dey
bne -""")
}
"|" -> {
asmgen.out("""
lda $targetVar
ora $sourceVar
sta $targetVar
lda $targetVar+1
ora $sourceVar+1
sta $targetVar+1
lda $targetVar+2
ora $sourceVar+2
sta $targetVar+2
lda $targetVar+3
ora $sourceVar+3
sta $targetVar+3""")
}
"&" -> {
asmgen.out("""
lda $targetVar
and $sourceVar
sta $targetVar
lda $targetVar+1
and $sourceVar+1
sta $targetVar+1
lda $targetVar+2
and $sourceVar+2
sta $targetVar+2
lda $targetVar+3
and $sourceVar+3
sta $targetVar+3""")
}
"^" -> {
asmgen.out("""
lda $targetVar
eor $sourceVar
sta $targetVar
lda $targetVar+1
eor $sourceVar+1
sta $targetVar+1
lda $targetVar+2
eor $sourceVar+2
sta $targetVar+2
lda $targetVar+3
eor $sourceVar+3
sta $targetVar+3""")
}
else -> {
TODO("in-place modify LONG with variable")
}
}
}
internal fun inplacemodificationLongWithLiteralval(variable: String, operator: String, value: Int) {
fun inplaceLongShiftLeft() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
asl $variable
rol $variable+1
rol $variable+2
rol $variable+3""")
}
}
value in 3..7 -> {
asmgen.out("""
ldy #$value
- asl $variable
rol $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
value == 8 -> {
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable""")
}
value in 9..15 -> {
val shift = value - 8
asmgen.out("""
lda $variable+2
sta $variable+3
lda $variable+1
sta $variable+2
lda $variable
sta $variable+1
lda #0
sta $variable
ldy #$shift
- asl $variable+1
rol $variable+2
rol $variable+3
dey
bne -""")
}
value == 16 -> {
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1""")
}
value in 17..23 -> {
val shift = value-16
asmgen.out("""
lda $variable+1
sta $variable+3
lda $variable
sta $variable+2
lda #0
sta $variable
sta $variable+1
ldy #$shift
- asl $variable+2
rol $variable+3
dey
bne -""")
}
value == 24 -> {
asmgen.out("""
lda $variable
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
value <= 31 -> {
val shift = value-24
asmgen.out("""
lda $variable
ldy #$shift
- asl a
dey
bne -
sta $variable+3
lda #0
sta $variable
sta $variable+1
sta $variable+2""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
fun inplaceLongShiftRight() {
when {
value in 0..2 -> {
repeat(value) {
asmgen.out("""
lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable""")
}
}
// TODO optimize for more cases 8, 16, 24 etc but don't forget to take the sign bit into account!
value <= 31 -> {
asmgen.out("""
ldy #$value
- lda $variable+3
asl a ; save sign bit
ror $variable+3
ror $variable+2
ror $variable+1
ror $variable
dey
bne -""")
}
else -> {
asmgen.out("""
lda #0
sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
}
}
}
when(operator) {
"+" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
inc $variable
bne +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
clc
lda $variable
adc #$value
sta $variable
bcc +
inc $variable+1
bne +
inc $variable+2
bne +
inc $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
clc
lda $variable
adc #<$value
sta $variable
lda $variable+1
adc #>$value
sta $variable+1
bcc +
inc $variable+2
bne +
inc $variable+3
+""")
} else {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
clc
lda $variable
adc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
adc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
adc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
adc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"-" -> {
when(value) {
0 -> {}
1 -> {
asmgen.out("""
lda $variable
bne +
dec $variable+1
bne +
dec $variable+2
bne +
dec $variable+3
+ dec $variable""")
}
else -> {
if(value in 1..255) {
asmgen.out("""
lda $variable
sec
sbc #$value
sta $variable
bcs +
dec $variable+1
bne +
dec $variable+2
bne +
dec $variable+3
+""")
} else if(value in 1..65535) {
asmgen.out("""
lda $variable
sec
sbc #<$value
sta $variable
lda $variable+1
sbc #>$value
sta $variable+1
bcs +
dec $variable+2
bne +
dec $variable+3
+""")
} else {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
sec
lda $variable
sbc #$${hex.substring(6,8)}
sta $variable
lda $variable+1
sbc #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
sbc #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
sbc #$${hex.take(2)}
sta $variable+3""")
}
}
}
}
"<<" -> if (value > 0) inplaceLongShiftLeft()
">>" -> if (value > 0) inplaceLongShiftRight()
"|" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
ora #$${hex.substring(6,8)}
sta $variable
lda $variable+1
ora #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
ora #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
ora #$${hex.take(2)}
sta $variable+3""")
}
"&" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
and #$${hex.substring(6,8)}
sta $variable
lda $variable+1
and #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
and #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
and #$${hex.take(2)}
sta $variable+3""")
}
"^" -> {
val hex = value.toUInt().toString(16).padStart(8, '0')
asmgen.out("""
lda $variable
eor #$${hex.substring(6,8)}
sta $variable
lda $variable+1
eor #$${hex.substring(4, 6)}
sta $variable+1
lda $variable+2
eor #$${hex.substring(2, 4)}
sta $variable+2
lda $variable+3
eor #$${hex.take(2)}
sta $variable+3""")
}
else -> {
TODO("inplace long $operator $value")
}
}
}
private fun tryIndexedIncDec(array: PtArrayIndexer, operator: String): Boolean {
val arrayVar = array.variable
if(arrayVar==null) {
@@ -1851,7 +2323,7 @@ $shortcutLabel:""")
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $lsb | ldy $msb | jsr prog8_math.mul_word_$value | sta $lsb | sty $msb")
} else {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware mul
asmgen.out("""
lda $lsb
@@ -1862,9 +2334,10 @@ $shortcutLabel:""")
ldy #>$value
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $lsb
sty $msb""")
}
else
asmgen.out("""
lda $lsb
@@ -2349,9 +2822,10 @@ $shortcutLabel:""")
ldy $name+1
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
} else {
if(valueDt.isUnsignedByte) {
asmgen.out(" lda $otherName | sta prog8_math.multiply_words.multiplier")
@@ -2494,7 +2968,7 @@ $shortcutLabel:""")
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
"*" -> {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
lda $name
@@ -2505,9 +2979,10 @@ $shortcutLabel:""")
ldy $otherName+1
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
lda $otherName
@@ -2698,7 +3173,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
require(dt.isWord)
fun multiplyVarByWordInAX() {
if(block?.options?.veraFxMuls==true)
if(block?.options?.veraFxMuls==true) {
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
@@ -2707,9 +3182,10 @@ $shortcutLabel:""")
ldx $name+1
sta cx16.r0
stx cx16.r0+1
jsr verafx.muls
jsr verafx.muls16
sta $name
sty $name+1""")
}
else
asmgen.out("""
sta prog8_math.multiply_words.multiplier
@@ -3003,7 +3479,7 @@ $shortcutLabel:""")
if(value is PtNumber && value.number<=255) {
TODO("shift a word var by ${value.number}")
} else {
throw AssemblyError("shift by a word value not supported, max is a byte")
throw AssemblyError("bit shift value can not be larger than a byte")
}
}
"&" -> {
@@ -257,16 +257,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
when (operator) {
"+" -> {
if(target.dt.isByte) inplaceByteAdd(target, value)
else if(target.dt.isWord) inplaceWordAdd(target, value)
else if(target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteInc(target, amount)
} else {
if (target.dt.isByte) inplaceByteAdd(target, value)
else if (target.dt.isWord) inplaceWordAdd(target, value)
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
}
"-" -> {
if(target.dt.isByte) inplaceByteSub(target, value)
else if(target.dt.isWord) inplaceWordSub(target, value)
else if(target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
val amount = value.number.number.toInt()
inplaceByteDec(target, amount)
} else {
if (target.dt.isByte) inplaceByteSub(target, value)
else if (target.dt.isWord) inplaceWordSub(target, value)
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else throw AssemblyError("weird dt ${target.position}")
}
}
"*" -> {
if(target.dt.isByte) TODO("inplaceByteMul(target, value) ${target.position}")
@@ -282,22 +292,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
"%" -> TODO("inplace ptr %")
"<<" -> {
if(target.dt.isByte) TODO("inplaceByteShiftLeft(target, value) ${target.position}")
if(target.dt.isByte) inplaceByteShiftLeft(target, value)
else if(target.dt.isWord) inplaceWordShiftLeft(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
">>" -> {
if(target.dt.isByte) TODO("inplaceByteShiftRight(target, value) ${target.position}")
if(target.dt.isByte) inplaceByteShiftRight(target, value)
else if(target.dt.isWord) inplaceWordShiftRight(target, value)
else throw AssemblyError("weird dt ${target.position}")
}
"&", "and" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr &")
if(target.dt.isByteOrBool) inplaceByteAnd(target, value)
else if(target.dt.isWord) inplaceWordAnd(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"|", "or" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
TODO("inplace ptr |")
if(target.dt.isByteOrBool) inplaceByteOr(target, value)
else if(target.dt.isWord) inplaceWordOr(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"^", "xor" -> {
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
@@ -305,12 +319,6 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
else if(target.dt.isWord) inplaceWordXor(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
"==" -> TODO("inplace ptr ==")
"!=" -> TODO("inplace ptr !=")
"<" -> TODO("inplace ptr <")
"<=" -> TODO("inplace ptr <=")
">" -> TODO("inplace ptr >")
">=" -> TODO("inplace ptr >=")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -398,6 +406,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
TODO("array ptr assign const word ${target.position}")
}
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
TODO("array ptr assign const long ${target.position}")
}
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
TODO("array ptr assign const float ${target.position}")
}
@@ -434,6 +446,10 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
TODO("array ptr assign word var ${target.position}")
}
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
TODO("array ptr assign long var ${target.position}")
}
internal fun operatorDereference(binExpr: PtBinaryExpression): Triple<String, UByte, DataType> {
// the only case we support here is: a.b.c[i] . value
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero), and finally the datatype of the field
@@ -491,10 +507,9 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
private fun addUnsignedByteOrWordToAY(value: PtExpression, scale: Int?) {
// TODO generate more optimal code from this, a bit too much stack and temporary variable juggling seems to be done in some easy cases...
fun complicatedFallback() {
// slow fallback routine that can deal with any expression for value
// slow fallback routine that can deal with any expression for value (const number and variable have been dealt with already)
fun restoreAYandAddAsmVariable(varname: String, isByte: Boolean) {
asmgen.restoreRegisterStack(CpuRegister.Y, false) // msb
@@ -693,7 +708,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
val (zpPtrVar, offset) = deref(target.pointer)
if(target.dt.isSigned)
TODO("signed word shift rigth ${target.position} $value")
TODO("signed word shift right ${target.position} $value")
fun shift1unsigned() {
asmgen.out("""
@@ -720,21 +735,92 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val varname = value.asmVarname
TODO("<< variable")
asmgen.out(" ldx $varname")
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
TODO("<< expression")
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
require(register.isWord())
TODO("<< register")
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
shift1unsigned()
asmgen.out(" dex | bne -")
}
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteShiftRight(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
if(target.dt.isSigned)
TODO("signed byte shift right ${target.position} $value")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
lsr a
sta ($zpPtrVar),y""")
} else if(number>1) {
asmgen.out("""
ldx #$number
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isByte)
val varname = value.asmVarname
asmgen.out("""
ldx $varname
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, null, target.position, register = RegisterOrPair.X))
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- lsr a
dex
bne -
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird source value $value")
}
@@ -768,21 +854,90 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val varname = value.asmVarname
TODO("<< variable")
asmgen.out(" ldx $varname")
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
TODO("<< expression")
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isWord)
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
require(register.isWord())
TODO("<< register")
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
shift1()
asmgen.out(" dex | bne -")
}
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteShiftLeft(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(number==1) {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
asl a
sta ($zpPtrVar),y""")
} else if(number>1) {
asmgen.out("""
ldx #$number
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isByte)
val varname = value.asmVarname
asmgen.out("""
ldx $varname
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isByte)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> {
require(value.datatype.isByte)
val register = value.register!!
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
asmgen.out("-")
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
- asl a
dex
bne -
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird source value $value")
}
@@ -1046,6 +1201,46 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
private fun inplaceByteInc(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" inc a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
clc
adc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteDec(target: PtrTarget, amount: Int) {
require(amount==1 || amount==2)
val (zpPtrVar, offset) = deref(target.pointer)
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar)")
repeat(amount) {
asmgen.out(" dec a")
}
asmgen.out(" sta ($zpPtrVar)")
}
else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
sec
sbc #$amount
sta ($zpPtrVar),y""")
}
}
private fun inplaceByteAdd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
@@ -1230,48 +1425,222 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
when {
target.datatype.isByteOrBool -> {
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.A, target)
}
target.datatype.isWord || target.datatype.isPointer -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
private fun inplaceByteOr(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
lda (P8ZP_SCRATCH_PTR)""")
lda ($zpPtrVar)
ora #$number
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #1
lda (P8ZP_SCRATCH_PTR),y
tax
dey
lda (P8ZP_SCRATCH_PTR),y""")
asmgen.assignRegister(RegisterOrPair.AX, target)
ldy #$offset
lda ($zpPtrVar),y
ora #$number
sta ($zpPtrVar),y""")
}
target.datatype.isLong -> {
TODO("assign long from pointer to $target ${target.position}")
SourceStorageKind.VARIABLE -> {
val varname = value.asmVarname
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
ora $varname
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora $varname
sta ($zpPtrVar),y""")
}
target.datatype.isFloat -> {
// TODO optimize the float copying to avoid having to go through FAC1
SourceStorageKind.EXPRESSION -> {
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
asmgen.out("""
lda P8ZP_SCRATCH_PTR
ldy P8ZP_SCRATCH_PTR+1
jsr floats.MOVFM""")
asmgen.assignRegister(RegisterOrPair.FAC1, target)
ldy #$offset
ora ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
else -> throw AssemblyError("weird dt ${target.datatype}")
SourceStorageKind.REGISTER -> TODO("register | byte")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceWordOr(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora #<$number
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
ora #>$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
ora $varname
sta ($zpPtrVar),y
lda ($zpPtrVar),y
ora $varname+1
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
asmgen.out("""
ldy #$offset
ora ($zpPtrVar),y
sta ($zpPtrVar),y
iny
txa
ora ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register | word")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceByteAnd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
and #$number
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and #$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
val varname = value.asmVarname
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out("""
lda ($zpPtrVar)
and $varname
sta ($zpPtrVar)""")
else
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and $varname
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
asmgen.out("""
ldy #$offset
and ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register & byte")
else -> throw AssemblyError("weird source value $value")
}
}
private fun inplaceWordAnd(target: PtrTarget, value: AsmAssignSource) {
val (zpPtrVar, offset) = deref(target.pointer)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and #<$number
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
and #>$number
sta ($zpPtrVar),y""")
}
SourceStorageKind.VARIABLE -> {
require(value.datatype.isWord)
val varname = value.asmVarname
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
and $varname
sta ($zpPtrVar),y
lda ($zpPtrVar),y
and $varname+1
sta ($zpPtrVar),y""")
}
SourceStorageKind.EXPRESSION -> {
require(value.datatype.isWord)
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
asmgen.out("""
ldy #$offset
and ($zpPtrVar),y
sta ($zpPtrVar),y
iny
txa
and ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
SourceStorageKind.REGISTER -> TODO("register & word")
else -> throw AssemblyError("weird source value $value")
}
}
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
// when {
// target.datatype.isByteOrBool -> {
// asmgen.out("""
// ldy #0
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.A, target)
// }
// target.datatype.isWord || target.datatype.isPointer -> {
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// lda (P8ZP_SCRATCH_PTR)""")
// else
// asmgen.out("""
// ldy #1
// lda (P8ZP_SCRATCH_PTR),y
// tax
// dey
// lda (P8ZP_SCRATCH_PTR),y""")
// asmgen.assignRegister(RegisterOrPair.AX, target)
// }
// target.datatype.isLong -> {
// TODO("assign long from pointer to $target ${target.position}")
// }
// target.datatype.isFloat -> {
// // TODO optimize the float copying to avoid having to go through FAC1
// asmgen.out("""
// lda P8ZP_SCRATCH_PTR
// ldy P8ZP_SCRATCH_PTR+1
// jsr floats.MOVFM""")
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
// }
// else -> throw AssemblyError("weird dt ${target.datatype}")
// }
}
private fun saveOnStack(regs: RegisterOrPair) {
when(regs) {
RegisterOrPair.AX -> {
+2
View File
@@ -12,12 +12,14 @@ internal object DummyMemsizer : IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
+1
View File
@@ -31,6 +31,7 @@ class TestCodegen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -76,6 +76,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
in combinedLongRegisters -> {
require(returns.type.isLong)
val startreg = returns.register.registerOrPair!!.startregname()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1=regNum, labelSymbol = "cx16.${startreg}"), null)
}
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum), null)
null -> if(returns.register.statusflag!=null)
@@ -137,63 +142,75 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val oldvalueReg = codeGen.registers.next(targetDt)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
inplaceInstrs += operandTr.chunks
}
if(targetDt== IRDataType.FLOAT) {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
if((augAssign.operator=="+=" || augAssign.operator=="-=") && value.asConstInteger()==1 || value.asConstInteger()==2) {
// INC/DEC optimization instead of ADD/SUB
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC
repeat(value.asConstInteger()!!) {
addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null)
}
} else {
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
operandTr = expressionEval.translateExpression(value)
// note: the instructions to load the value will be placed after the LOADFIELD instruction so that later optimizations about what modification is actually done, are easier
}
if(targetDt== IRDataType.FLOAT) {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
} else {
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
inplaceInstrs += operandTr.chunks
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = expressionEval.translateExpression(value)
inplaceInstrs += valueTr.chunks
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
"+" -> { /* inplace + is a no-op */ }
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
}
@@ -222,6 +239,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return chunks
}
private fun loadfield(
inplaceInstrs: MutableList<IRCodeChunkBase>,
addressReg: Int,
fieldOffset: UByte,
targetDt: IRDataType,
oldvalueReg: Int
) {
if (targetDt == IRDataType.FLOAT) {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg),
null
)
} else {
if (fieldOffset > 0u)
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()),
null
)
else
addInstr(
inplaceInstrs,
IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg),
null
)
}
}
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
val value: PtExpression
if(origAssign.operator in PrefixOperators) {
@@ -415,10 +468,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
val targetIdent = assignment.target.identifier
val targetMemory = assignment.target.memory
val targetArray = assignment.target.array
val targetPointerDeref = assignment.target.pointerDeref
val valueDt = irType(assignment.value.type)
val targetDt = irType(assignment.target.type)
val result = mutableListOf<IRCodeChunkBase>()
@@ -459,98 +508,104 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
if(targetIdent!=null) {
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
} else {
if (targetDt == IRDataType.FLOAT) {
require(valueFpRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
}
else {
require(valueRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
}
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
else if(targetArray!=null) {
val eltSize = codeGen.program.memsizer.memorySize(targetArray.type, null)
val variable = targetArray.variable
if(variable==null)
translateRegularAssignPointerIndexed(result, targetArray.pointerderef!!, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
else if(variable.type.isPointer)
assignToIndexedSimplePointer(result, variable, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
else
translateRegularAssignArrayIndexed(result, variable.name, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
return result
}
else if(targetMemory!=null) {
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
if(zero) {
if(targetMemory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
}
}
} else {
val constAddress = targetMemory.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
}
return result
with(assignment.target) {
when {
identifier != null -> {
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = identifier!!.name)
} else {
if (targetDt == IRDataType.FLOAT) {
require(valueFpRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = identifier!!.name)
}
else {
require(valueRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = identifier!!.name)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
expressionEval.translateExpression(offsetTypecast.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
result += IRCodeChunk(null, null).also { it += instruction }
return result
}
memory != null -> {
require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"}
if(zero) {
if(memory!!.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (memory!!.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(memory!!.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
}
}
} else {
val constAddress = memory!!.address as? PtNumber
if(constAddress!=null) {
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
return result
}
val ptrWithOffset = memory!!.address as? PtBinaryExpression
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
}
return result
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
expressionEval.translateExpression(offsetTypecast.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return result
}
}
val tr = expressionEval.translateExpression(memory!!.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
return result
}
return result
}
array != null -> {
val eltSize = codeGen.program.memsizer.memorySize(array!!.type, null)
val variable = array!!.variable
if(variable==null)
translateRegularAssignPointerIndexed(result, array!!.pointerderef!!, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
else if(variable.type.isPointer)
assignToIndexedSimplePointer(result, variable, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
else
translateRegularAssignArrayIndexed(result, variable.name, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
return result
}
pointerDeref != null -> {
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, pointerDeref!!)
val actualValueReg = if(pointerDeref!!.type.isFloat) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, addressReg, offset, pointerDeref!!.type, zero, actualValueReg)
return result
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
return result
}
else -> {
throw AssemblyError("weird assigntarget")
}
}
return result
}
else if(targetPointerDeref!=null) {
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
val actualValueReg = if(targetPointerDeref.type.isFloat) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, addressReg, offset, targetPointerDeref.type, zero, actualValueReg)
return result
}
else
throw AssemblyError("weird assigntarget")
}
private fun assignToIndexedSimplePointer(
@@ -1054,14 +1109,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(constAddress==null && memory!=null)
return null // TODO("optimized memory in-place -"")
val constValue = (operand as? PtNumber)?.number
val result = mutableListOf<IRCodeChunkBase>()
if(vmDt==IRDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
else
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
, null)
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
}
else {
val tr = expressionEval.translateExpression(operand)
@@ -1069,16 +1124,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, address = constAddress)
else
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol)
, null)
IRInstruction(Opcode.SUBM, vmDt, fpReg1=tr.resultFpReg, labelSymbol = symbol), null)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.DECM, vmDt, address = constAddress)
else
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol)
, null)
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
}
else {
val tr = expressionEval.translateExpression(operand)
@@ -1086,8 +1139,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, address = constAddress)
else
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
, null)
IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null)
}
}
return result
@@ -1153,9 +1205,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return null // TODO("optimized memory in-place +"")
val result = mutableListOf<IRCodeChunkBase>()
val constValue = (operand as? PtNumber)?.number
if(vmDt==IRDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if (constAddress != null)
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
else
@@ -1170,7 +1223,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
IRInstruction(Opcode.ADDM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol) , null)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
if(constValue==1.0) {
addInstr(result, if (constAddress != null)
IRInstruction(Opcode.INCM, vmDt, address = constAddress)
else
@@ -1,5 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StMemorySlabBlockName
import prog8.code.StStructInstanceBlockName
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
@@ -12,7 +14,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return when(call.name) {
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
@@ -22,25 +24,28 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"callfar" -> funcCallfar(call)
"callfar2" -> funcCallfar2(call)
"call" -> funcCall(call)
"lsw" -> throw AssemblyError("lsw() should have been removed or replaced by a const value")
"msw" -> throw AssemblyError("msw() should have been removed or replaced by a const value")
"msw" -> funcMsw(call)
"lsw" -> funcLsw(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekbool" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekl" -> funcPeek(call, IRDataType.LONG)
"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)
"pokel" -> funcPoke(call, IRDataType.LONG)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
"mkword" -> funcMkword(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"mklong", "mklong2" -> funcMklong(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword", "clamp__long" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword", "min__long" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword", "max__long" -> funcMax(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
@@ -52,6 +57,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"prog8_lib_structalloc" -> funcStructAlloc(call)
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
@@ -201,6 +207,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
}
BaseDataType.LONG -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
@@ -250,6 +267,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
BaseDataType.LONG -> TODO("SQRT LONG")
BaseDataType.FLOAT -> {
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
@@ -280,6 +298,52 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(IRDataType.LONG)
if(call.args.size==2) {
// mklong2(word, word)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msw is 0, use EXT
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
} else {
val mswTr = exprGen.translateExpression(call.args[0])
addToResult(result, mswTr, mswTr.resultReg, -1)
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
}
} else {
// mklong(msb, b3, b2, lsb)
if((call.args[0] as? PtNumber)?.number == 0.0 && (call.args[1] as? PtNumber)?.number == 0.0 && (call.args[2] as? PtNumber)?.number == 0.0) {
// use EXT.b + EXT.w
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val wordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=wordReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = wordReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
val b2Tr = exprGen.translateExpression(call.args[1])
val b1Tr = exprGen.translateExpression(call.args[2])
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, msbTr, msbTr.resultReg, -1)
addToResult(result, b2Tr, b2Tr.resultReg, -1)
addToResult(result, b1Tr, b1Tr.resultReg, -1)
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val lswReg = codeGen.registers.next(IRDataType.WORD)
val mswReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
}
}
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val type = irType(call.type)
@@ -304,6 +368,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
result += codeGen.makeSyscall(syscall, listOf(
@@ -500,7 +565,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
@@ -508,7 +573,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
}
@@ -522,6 +587,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
@@ -532,6 +606,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
private fun funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.MSIG, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val arg = call.args[0]
@@ -446,7 +446,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else (it as PtNumber).number.toInt()
}
when {
elementDt.isIntegerOrBool -> {
elementDt.isWordOrByteOrBool -> {
if (elementDt.isByteOrBool) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
if (elementDt.isWord) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
val gottemLabel = codeGen.createLabelName()
@@ -686,6 +686,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null)
actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result)
}
valueDt.isLong -> TODO("typecast long ${cast.position}")
valueDt.isFloat -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
@@ -705,6 +706,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> TODO("cast UBYTE to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -721,6 +723,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LSIG, IRDataType.BYTE, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null)
}
BaseDataType.LONG -> TODO("cast BYTE to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -743,6 +746,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.WORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> TODO("cast UWORD to LONG ${cast.position}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -768,6 +772,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
BaseDataType.UWORD -> {
actualResultReg2 = tr.resultReg
}
BaseDataType.LONG -> TODO("cast WORD to LONG ${cast.position}}")
BaseDataType.FLOAT -> {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
@@ -775,6 +780,33 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.LONG -> {
when(valueDt.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> {
// ubyte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.BYTE -> {
// byte to long: double sign extend
val wordreg = codeGen.registers.next(IRDataType.WORD)
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = wordreg, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=wordreg), null)
}
BaseDataType.UWORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
BaseDataType.WORD -> {
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
else -> throw AssemblyError("weird cast value type ${cast.position}")
}
}
BaseDataType.FLOAT -> {
actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT)
when(valueDt.base) {
@@ -47,6 +47,7 @@ class IRCodeGen(
ensureFirstChunkLabels(irProg)
irProg.linkChunks()
irProg.convertAsmChunks()
irProg.splitSSAchunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
@@ -1975,12 +1976,17 @@ class IRCodeGen(
in Cx16VirtualRegisters -> {
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
}
in combinedLongRegisters -> {
require(paramDt==IRDataType.LONG)
val startreg = registerOrFlag.registerOrPair!!.startregname()
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}")
}
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")
else -> throw AssemblyError("unsupported register arg $registerOrFlag")
}
return chunk
}
@@ -161,10 +161,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
// if the previous chunk doesn't end in a SSA branching instruction, flow continues into the next chunk, so they may be joined
val lastInstruction = previous.instructions.lastOrNull()
if(lastInstruction!=null)
return lastInstruction.opcode !in OpcodesThatJump
return lastInstruction.opcode !in OpcodesThatEndSSAblock
return true
}
return false
@@ -300,16 +300,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// remove useless RETURN
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally) {
chunk.instructions.removeAt(idx)
changed = true
}
}
// replace subsequent opcodes that jump by just the first
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
if(previous.opcode in OpcodesThatBranchUnconditionally) {
chunk.instructions.removeAt(idx)
changed = true
}
@@ -362,7 +362,7 @@ jump p8_label_gen_2
*/
var changed=false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>=2 && ins.opcode in OpcodesThatJump) {
if(idx>=2 && ins.opcode in OpcodesThatBranchUnconditionally) {
val previous = indexedInstructions[idx-1].value
val previous2 = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) {
@@ -7,6 +7,7 @@ internal class RegisterPool {
// 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)
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
private var nextRegister: Int=1
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
@@ -18,6 +19,8 @@ internal class RegisterPool {
registerTypes[i] = IRDataType.WORD
for(i in 99100..99199)
registerTypes[i] = IRDataType.BYTE
for(i in 99200..99299)
registerTypes[i] = IRDataType.LONG
}
fun next(type: IRDataType): Int {
@@ -47,10 +47,16 @@ 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 convertArrayElt(elt: StArrayElement): IRStArrayElement {
return if (elt.boolean != null)
IRStArrayElement(elt.boolean, null, null)
else if(elt.number!=null)
IRStArrayElement(null, elt.number, null)
else {
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
IRStArrayElement(null, null, symbol)
}
}
private fun convert(variable: StStaticVariable): IRStStaticVariable {
@@ -72,7 +78,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
val newArray = mutableListOf<IRStArrayElement>()
array.forEach {
if(it.addressOfSymbol!=null) {
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
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))
@@ -129,11 +136,11 @@ private fun convert(constant: StConstant): IRStConstant {
}
private fun convert(variable: StMemorySlab): IRStMemorySlab {
return if('.' in variable.name)
IRStMemorySlab(variable.name, variable.size, variable.align)
private fun convert(mem: StMemorySlab): IRStMemorySlab {
return if('.' in mem.name)
IRStMemorySlab(mem.name, mem.size, mem.align)
else
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align)
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
@@ -142,8 +149,8 @@ private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType,
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?
+2
View File
@@ -10,12 +10,14 @@ internal object DummyMemsizer : IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
@@ -23,6 +23,7 @@ class TestIRPeepholeOpt: FunSpec({
noSysInit = true,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
+3 -2
View File
@@ -25,6 +25,7 @@ class TestVmCodeGen: FunSpec({
noSysInit = false,
romable = false,
compTarget = target,
compilerVersion="99.99",
loadAddress = target.PROGRAM_LOAD_ADDRESS,
memtopAddress = 0xffffu
)
@@ -340,7 +341,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks.size shouldBe 2
}
test("integer comparison expressions against zero") {
@@ -536,7 +537,7 @@ class TestVmCodeGen: FunSpec({
val errors = ErrorReporterForTests()
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks.size shouldBe 2
}
test("extsub allowed in ir-codegen") {
@@ -39,7 +39,10 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=BaseDataType.LONG) {
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
// do NOT reduce the type if the target of the assignment is a long
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
}
}
@@ -85,6 +88,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val modifications = mutableListOf<IAstModification>()
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -438,7 +443,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(valueDt issimpletype BaseDataType.LONG) {
if(valueDt issimpletype BaseDataType.LONG || decl.datatype.isLong) {
return noModifications // this is handled in the numericalvalue case
}
if(!(valueDt istype decl.datatype)) {
@@ -26,19 +26,15 @@ class VarConstantValueTypeAdjuster(
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype.base) {
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
// avoid silent float roundings
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
}
// replace variables by constants, if possible
@@ -173,7 +169,12 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = if(t1.isBytes) {
if(t1 issimpletype BaseDataType.BYTE) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
if(t1 issimpletype BaseDataType.WORD) "clamp__word" else "clamp__uword"
when {
t1 issimpletype BaseDataType.WORD -> "clamp__word"
t1 issimpletype BaseDataType.UWORD -> "clamp__uword"
t1 issimpletype BaseDataType.LONG -> "clamp__long"
else -> throw FatalAstException("clamp type")
}
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@@ -195,10 +196,12 @@ class VarConstantValueTypeAdjuster(
else
"${funcName}__ubyte"
} else if(t1.isInteger && t2.isInteger) {
replaceFunc = if(t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD)
"${funcName}__word"
else
"${funcName}__uword"
replaceFunc = when {
t1 issimpletype BaseDataType.LONG || t2 issimpletype BaseDataType.LONG -> "${funcName}__long"
t1 issimpletype BaseDataType.WORD || t2 issimpletype BaseDataType.WORD -> "${funcName}__word"
t1 issimpletype BaseDataType.UWORD || t2 issimpletype BaseDataType.UWORD -> "${funcName}__uword"
else -> throw FatalAstException("min/max type")
}
} else if(t1.isNumeric && t2.isNumeric) {
errors.err("min/max not supported for floats", functionCallExpr.position)
return noModifications
@@ -218,6 +221,7 @@ class VarConstantValueTypeAdjuster(
val replaceFunc = when {
dt.isSignedByte -> "abs__byte"
dt.isSignedWord -> "abs__word"
dt.isLong -> "abs__long"
dt.isFloat -> "abs__float"
dt.isUnsignedByte || dt.isUnsignedWord -> {
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
@@ -313,41 +317,36 @@ internal class ConstantIdentifierReplacer(
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
return noModifications
try {
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
val cval = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
}
when {
cval.type.isNumericOrBool -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
}
when {
cval.type.isNumericOrBool -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent
)
}
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
)
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
}
}
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.log2
@@ -90,6 +91,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val newExpr = applyAbsorptionLaws(expr)
if(newExpr!=null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -244,15 +247,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (rightVal?.number == 1.0) {
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -266,15 +273,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if (rightVal?.number == 1.0) {
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
else if (rightVal?.number == 0.0) {
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
if(!dt.isLong) {
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
}
}
}
}
@@ -424,19 +435,92 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
}
// fun isPowerOfTwo(number: Double): Boolean {
// val intValue = number.toInt()
// return intValue > 0 && (intValue and (intValue - 1)) == 0
// }
fun isFactorOf256(number: Double): Boolean {
val intValue = number.toInt()
return intValue >= 256 && (intValue and 0xFF) == 0
}
if (leftDt.isUnsignedWord && rightVal!=null) {
if (expr.operator == ">" && rightVal.number == 255.0 || expr.operator == ">=" && rightVal.number == 256.0) {
// uword > 255 --> msb(value)!=0
// uword >= 256 --> msb(value)!=0
expr.operator = "!="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">=" && isFactorOf256(rightVal.number)) {
// uword >= $xx00 --> msb(value)>=xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = ">="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator==">" && isFactorOf256(rightVal.number+1)) {
// uword > $xxFF --> msb(value)>xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = ">"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && rightVal.number == 256.0 || expr.operator == "<=" && rightVal.number == 255.0) {
// uword < 256 --> msb(value)==0
// uword <= 255 --> msb(value)==0
expr.operator = "=="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator == "<" && isFactorOf256(rightVal.number)) {
// uword < $xx00 --> msb(value)<xx
val msbFraction = floor(rightVal.number / 256.0)
expr.operator = "<"
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
else if(expr.operator=="<=" && isFactorOf256(rightVal.number+1)) {
// uword <= $xxFF --> msb(value)<=xx
val msbFraction = floor((rightVal.number) / 256.0)
expr.operator = "<="
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
expr.linkParents(parent)
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.plainarrayvar!=null) {
if((arrayIndexedExpression.parent as? BinaryExpression)?.operator !=".") {
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
if(binexprParent?.operator!=".") {
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
if(dt.isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
}
} else if(arrayIndexedExpression.pointerderef==null) {
// possibly pointer[0].field --> pointer.field
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(target?.datatype?.isPointer==true) {
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
if(field!=null) {
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
}
}
}
}
val ptrDeref = arrayIndexedExpression.pointerderef
@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.compiler.CallGraph
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
+2 -1
View File
@@ -2,7 +2,8 @@ plugins {
id("application")
kotlin("jvm")
// id("com.github.johnrengelman.shadow") version "8.1.1"
id("io.github.goooler.shadow") version "8.1.8"
// id("io.github.goooler.shadow") version "8.1.8"
id("com.gradleup.shadow") version "9.2.2"
id("com.peterabeles.gversion") version "1.10.3"
}
+82
View File
@@ -0,0 +1,82 @@
bcd {
; Decimal addition an subtraction routines.
; For CPUs that support BCD mode (binary coded decimal) (not all 6502 variants support this mode...)
; This is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string. Just print the hex representation.
sub addb(byte a, byte b) -> byte {
setbcd()
a += b
clearbcd()
return a
}
sub addub(ubyte a, ubyte b) -> ubyte {
setbcd()
a += b
clearbcd()
return a
}
sub addw(word a, word b) -> word {
setbcd()
a += b
clearbcd()
return a
}
sub adduw(uword a, uword b) -> uword {
setbcd()
a += b
clearbcd()
return a
}
sub addl(long a, long b) -> long {
setbcd()
a += b
clearbcd()
return a
}
sub subb(byte a, byte b) -> byte {
setbcd()
a -= b
clearbcd()
return a
}
sub subub(ubyte a, ubyte b) -> ubyte {
setbcd()
a -= b
clearbcd()
return a
}
sub subuw(uword a, uword b) -> uword {
setbcd()
a -= b
clearbcd()
return a
}
sub subl(long a, long b) -> long {
setbcd()
a -= b
clearbcd()
return a
}
inline asmsub setbcd() {
%asm {{
php
sei
sed
}}
}
inline asmsub clearbcd() {
%asm {{
plp
}}
}
}
+137 -34
View File
@@ -133,6 +133,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
@@ -506,6 +529,7 @@ save_SCRATCH_ZPWORD2 .word ?
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
@@ -513,7 +537,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -545,6 +569,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -554,24 +579,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; enable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -593,34 +628,46 @@ _raster_irq_handler
pla
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
@@ -680,6 +727,26 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
cpy #0
bne _larger
- cmp c64.RASTER
bne -
bit c64.SCROLY
bmi -
rts
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
@@ -989,6 +1056,48 @@ _no_msb_size
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -1165,12 +1274,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
+140 -34
View File
@@ -132,6 +132,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
@@ -313,12 +336,13 @@ asmsub banks(ubyte banks @A) {
%asm {{
and #%00000111
sta P8ZP_SCRATCH_REG
php
sei
lda $01
and #%11111000
ora P8ZP_SCRATCH_REG
sta $01
cli
plp
rts
}}
}
@@ -511,6 +535,7 @@ save_SCRATCH_ZPWORD2 .word ?
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
@@ -518,7 +543,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
plp
rts
_irq_handler
jsr sys.save_prog8_internals
@@ -551,6 +576,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
@@ -560,24 +586,34 @@ asmsub restore_irq() clobbers(A) {
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
plp
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
jsr sys.set_rasterline
lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -600,33 +636,46 @@ _raster_irq_handler
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ; enable raster interrupt signals from vic
rts
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr sys.set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
; -- only set a new raster line for the raster IRQ
%asm {{
sta c64.RASTER ; set the raster line number where interrupt should occur
lda c64.SCROLY
and #%01111111
cpy #0
beq +
ora #%10000000
+ sta c64.SCROLY ; clear most significant bit of raster position
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
@@ -687,6 +736,26 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
cpy #0
bne _larger
- cmp c64.RASTER
bne -
bit c64.SCROLY
bmi -
rts
_larger
cmp c64.RASTER
bne _larger
bit c64.SCROLY
bpl _larger
rts
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
@@ -995,6 +1064,49 @@ _no_msb_size
pla
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; The SuperCPU expansion for the C64/C128 contains a 65816.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
cx16 {
@@ -1172,12 +1284,6 @@ cx16 {
rts
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
+108 -10
View File
@@ -155,6 +155,30 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
}}
}
sub str_ulhex(long value) -> str {
%asm {{
lda value+3
jsr internal_ubyte2hex
sta string_out
sty string_out+1
lda value+2
jsr internal_ubyte2hex
sta string_out+2
sty string_out+3
lda value+1
jsr internal_ubyte2hex
sta string_out+4
sty string_out+5
lda value
jsr internal_ubyte2hex
sta string_out+6
sty string_out+7
lda #0
sta string_out+8
}}
return &string_out
}
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
@@ -228,15 +252,95 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
}}
}
sub str_l (long value) -> str {
; ---- convert the long value into decimal string form, without left padding 0s
; source: https://codebase64.net/doku.php?id=base:32_bit_hexadecimal_to_decimal_conversion
; NOTE: this is a pretty slow conversion, don't use for time critical displays!
bool negative
if value<0 {
negative = true
value = -value
}
%asm {{
jsr hex2dec
ldx #0
- lda result,x
bne +
inx ; skip leading zeros
cpx #9
bne -
; convert to petscii numbers and return ptr to first nonzero
+ stx P8ZP_SCRATCH_B1
inc P8ZP_SCRATCH_B1
- lda result,x
ora #$30
sta result,x
inx
cpx #10
bne -
lda #0
sta result,x
; sign needed?
lda negative
beq +
dec P8ZP_SCRATCH_B1
ldx P8ZP_SCRATCH_B1
lda #'-'
sta result_sign,x
+ lda #<result_sign
ldy #>result_sign
clc
adc P8ZP_SCRATCH_B1
bcc +
iny
+ rts
; converts 10 digits (32 bit values have max. 10 decimal digits)
hex2dec
ldx #9
l3 jsr div10
sta result,x
dex
bpl l3
rts
; divides a 32 bit value by 10
; remainder is returned in akku
div10
ldy #32 ; 32 bits
lda #0
clc
l4 rol
cmp #10
bcc skip
sbc #10
skip rol value
rol value+1
rol value+2
rol value+3
dey
bpl l4
rts
.section BSS
result_sign .byte ? ; room for the '-' sign
result .fill 10
.byte ? ; string 0 terminator
.send
}}
}
; ---- string conversion to numbers -----
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
asmsub any2uword(str string @AY) -> uword @AY, ubyte @X {
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
; (the latter two require a $ or % prefix to be recognised)
; (any non-digit character will terminate the number string that is parsed)
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
; if the string was invalid, 0 will be returned in A.
; returns the parsed number word in AY, and the number of processed characters (including the prefix symbol) in X.
; if the string was invalid, 0 will be returned as count in X (and the word value in AY will be undefined).
%asm {{
pha
sta P8ZP_SCRATCH_W1
@@ -257,13 +361,7 @@ _hex pla
_bin pla
jsr bin2uword
_result
pha
lda cx16.r15
sta P8ZP_SCRATCH_B1 ; result value
pla
sta cx16.r15
sty cx16.r15+1
lda P8ZP_SCRATCH_B1
ldx cx16.r15
rts
}}
}
+1 -1
View File
@@ -283,7 +283,7 @@ palette {
; NOTE: this routine requires rom version 49+
alias set_default = cx16.set_default_palette
; get the bank and address of the word-array containing the 256 default palette colors
; get the bank and address (in A, and XY) of the word-array containing the 256 default palette colors
; NOTE: this routine requires rom version 49+
alias get_default = cx16.get_default_palette
}
+4 -1
View File
@@ -1,10 +1,13 @@
; Simple routines to control sprites.
; They're not written for high performance, but for simplicity.
; That's why they control 1 sprite at a time. \
; That's why they control 1 sprite at a time.
; The exceptions are pos_batch() and pos_batch_split(); these are quite efficient
; to update sprite positions of multiple sprites in one call.
; HIGH PERFORMANCE sprite handling would probably have a copy of the sprite registers for each sprite instead,
; and a unrolled loop that copies those into the VERA registers when needed.
; note: sprites z-order will be in front of all layers.
; note: collision mask is not supported here yet.
; note: "palette offset" is counted as 0-15 (vera multiplies the offset by 16 to get at the actual color index)
+116 -27
View File
@@ -127,6 +127,28 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM_safe() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM_safe
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
stz cx16.r0+3
rts
}}
}
sub CLEARST() {
; -- Set the ST status variable back to 0. (there's no direct kernal call for this)
; Note: a drive error state (blinking led) isn't cleared! You can use diskio.status() to clear that.
@@ -1454,22 +1476,6 @@ sub search_x16edit() -> ubyte {
return 255
}
asmsub cpu_is_65816() -> bool @A {
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
sub set_program_args(str args_ptr, ubyte args_size) {
; -- Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
@@ -1576,6 +1582,7 @@ sys {
asmsub set_irq(uword handler @AY) clobbers(A) {
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
%asm {{
php
sei
sta _vector
sty _vector+1
@@ -1585,7 +1592,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta cbm.CINV+1
lda #1
tsb cx16.VERA_IEN ; enable the vsync irq
cli
plp
rts
_irq_handler
@@ -1615,6 +1622,7 @@ _vector .word ?
asmsub restore_irq() clobbers(A) {
%asm {{
php
sei
lda _orig_irqvec
sta cbm.CINV
@@ -1624,7 +1632,7 @@ asmsub restore_irq() clobbers(A) {
and #%11110000 ; disable all Vera IRQs but the vsync
ora #%00000001
sta cx16.VERA_IEN
cli
plp
rts
.section BSS_NOCLEAR
_orig_irqvec .word ?
@@ -1636,9 +1644,10 @@ _orig_irqvec .word ?
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; Sets the handler for the LINE interrupt, and enable (only) that interrupt.
%asm {{
php
sei
sta _vector
sty _vector+1
sta user_vector
sty user_vector+1
lda cx16.r0
ldy cx16.r0+1
lda cx16.VERA_IEN
@@ -1652,7 +1661,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
plp
rts
_raster_irq_handler
@@ -1668,15 +1677,32 @@ _raster_irq_handler
pla
rti
_run_custom
jmp (_vector)
jmp (user_vector)
.section BSS
_vector .word ?
user_vector .word ?
.send BSS
; !notreached!
}}
}
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; -- just update the IRQ handler and raster line position for the raster IRQ
; this is much more efficient than calling set_rasterirq() again every time.
; (but you have to call that one initially at least once to setup the prog8 handler itself)
%asm {{
php
sei
sta sys.set_rasterirq.user_vector
sty sys.set_rasterirq.user_vector+1
lda cx16.r0L
ldy cx16.r0H
jsr set_rasterline
plp
rts
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
php
@@ -1732,13 +1758,15 @@ _loop lda P8ZP_SCRATCH_W1
bne +
rts
+ sei
+ php
sei
jsr cbm.RDTIM
cli
plp
sta P8ZP_SCRATCH_B1
- sei
- php
sei
jsr cbm.RDTIM
cli
plp
cmp P8ZP_SCRATCH_B1
beq -
@@ -1759,6 +1787,26 @@ _loop lda P8ZP_SCRATCH_W1
}}
}
asmsub waitrasterline(uword line @AY) {
; -- CPU busy wait until the given raster line is reached
%asm {{
cpy #0
bne _larger
- cmp cx16.VERA_SCANLINE_L
bne -
bit cx16.VERA_IEN
bvs -
rts
_larger
cmp cx16.VERA_SCANLINE_L
bne _larger
bit cx16.VERA_IEN
bvc _larger
rts
}}
}
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
@@ -2093,6 +2141,47 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
asmsub cpu_is_65816() -> bool @A {
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
%asm {{
php
clv
.byte $e2, $ea ; SEP #$ea, should be interpreted as 2 NOPs by 6502. 65c816 will set the Overflow flag.
bvc +
lda #1
plp
rts
+ lda #0
plp
rts
}}
}
}
p8_sys_startup {
+22 -17
View File
@@ -116,26 +116,29 @@ verafx {
asmsub mult16(uword value1 @R0, uword value2 @R1) clobbers(X) -> uword @AY {
; Returns the 16 bits unsigned result of R0*R1 in AY.
; Returns the lower 16 bits unsigned result of R0*R1 in AY
; Note: only the lower 16 bits! (the upper 16 bits are not valid for unsigned word multiplications, only for signed)
; Verafx doesn't support unsigned values like this for full 32 bit result.
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
sta P8ZP_SCRATCH_W1+1
jsr verafx.muls
ldx P8ZP_SCRATCH_W1
stx cx16.r0
ldx P8ZP_SCRATCH_W1+1
stx cx16.r0+1
jmp muls16
}}
}
asmsub muls16(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
; Returns just the lower 16 bits signed result of the multiplication in cx16.AY.
; Note: clobbers R0, R1, and VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
jsr muls
lda cx16.r0L
ldy cx16.r0H
rts
}}
}
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> long @R0R1_32 {
; Returns the 32 bits signed result in R0:R1 (lower word, upper word).
; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
@@ -171,12 +174,14 @@ verafx {
stz cx16.VERA_DATA0 ; multiply and write out result
lda #%00010001 ; $01 with Increment 1
sta cx16.VERA_ADDR_H ; so we can read out the result
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in AY
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in R0
ldy cx16.VERA_DATA0
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in R0
stx cx16.r0s
ldx cx16.VERA_DATA0
stx cx16.r0s+1
sta cx16.r0L
sty cx16.r0H
lda cx16.VERA_DATA0 ; store the upper 16 bits of the result in R1
ldy cx16.VERA_DATA0 ; store the upper 16 bits of the result in R1
sta cx16.r1L
sty cx16.r1H
stz cx16.VERA_FX_CTRL ; Cache write disable
stz cx16.VERA_FX_MULT ; $9F2C reset multiply bit
stz cx16.VERA_CTRL ; reset DCSEL
+13 -9
View File
@@ -220,6 +220,12 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
sub mul32(uword a, uword b) -> long {
; return 32 bits result of a*b
cx16.r2 = a*b
return mklong2(mul16_last_upper(), cx16.r2)
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
cx16.r0L = 3 ; quadrant
@@ -602,10 +608,9 @@ log2_tab
return cx16.r15
}
sub crc32(^^ubyte data, uword length) {
sub crc32(^^ubyte data, uword length) -> long {
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
; Clobbers R12 through R15, returns the 32 bits result as long
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
crc32_start()
cx16.r12 = data
@@ -614,7 +619,7 @@ log2_tab
crc32_update(@(cx16.r12))
cx16.r12++
}
crc32_end()
return crc32_end()
}
sub crc32_start() {
@@ -666,13 +671,12 @@ log2_tab
; }
}
sub crc32_end() {
; finalize the "streaming" crc32
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
void crc32_end_result()
sub crc32_end() -> long {
; finalize the "streaming" crc32 and returns the result
return crc32_end_result()
}
asmsub crc32_end_result() -> uword @R15, uword @R14 {
asmsub crc32_end_result() -> long @R14R15_32 {
; finalize the "streaming" crc32
; returns the result value in cx16.r15 (high word) and r14 (low word)
%asm {{
+54 -5
View File
@@ -81,6 +81,29 @@ asmsub RDTIM16() clobbers(X) -> uword @AY {
}}
}
asmsub SETTIML(long jiffies @R0R1_32) {
; -- just like SETTIM, but with a single 32 bit (lower 24 bits used) argument.
%asm {{
lda cx16.r0
ldx cx16.r0+1
ldy cx16.r0+2
jmp SETTIM
}}
}
asmsub RDTIML() clobbers(X) -> long @R0R1_32 {
; -- like RDTIM() and returning the timer value as a 32 bit (lower 24 bits used) value.
%asm {{
jsr RDTIM
sta cx16.r0
stx cx16.r0+1
sty cx16.r0+2
lda #0
sta cx16.r0+3
rts
}}
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
@@ -509,6 +532,37 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
inline asmsub pushl(long value @R0R1_32) {
%asm {{
lda cx16.r0
pha
lda cx16.r0+1
pha
lda cx16.r0+2
pha
lda cx16.r0+3
pha
}}
}
inline asmsub popl() -> long @R0R1_32 {
%asm {{
pla
sta cx16.r0+3
pla
sta cx16.r0+2
pla
sta cx16.r0+1
pla
sta cx16.r0
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
cx16 {
@@ -686,11 +740,6 @@ cx16 {
}}
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
p8_sys_startup {
+30
View File
@@ -29,6 +29,36 @@ abs_w_into_AY .proc
+ rts
.pend
abs_l_into_R0R1 .proc
; -- R0:R1 = abs(R0:R1)
lda cx16.r1H
bmi + ; Negative if high bit of highest byte is set
rts
+
; Invert all four bytes
lda cx16.r0L
eor #$FF
sta cx16.r0L
lda cx16.r0H
eor #$FF
sta cx16.r0H
lda cx16.r1L
eor #$FF
sta cx16.r1L
lda cx16.r1H
eor #$FF
sta cx16.r1H
; Add 1 to whole 32-bit value
inc cx16.r0L
bne +
inc cx16.r0H
bne +
inc cx16.r1L
bne +
inc cx16.r1H
+ rts
.pend
func_sign_b_into_A .proc
cmp #0
beq _zero
+192
View File
@@ -449,3 +449,195 @@ _fullpage lda (P8ZP_SCRATCH_W1),y
bne _fullpage
rts
.pend
long_not_equals .proc
; checks if the 32 bits long value pointed to by AY is NOT equal to the one pointed to by P8ZP_SCRATCH_W1
; returns A=1 if NOT equals otherwise A=0
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #3
- lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _notequal
dey
bpl -
lda #0
rts
_notequal lda #1
rts
.pend
long_add_inplace .proc
; long pointed to by AY += long pointed to by P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
clc
ldy #0
lda (P8ZP_SCRATCH_W2),y
adc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
adc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
adc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
adc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
long_sub_inplace .proc
; long pointed to by AY -= long pointed to by P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sec
lda (P8ZP_SCRATCH_W2),y
sbc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
sbc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
sbc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
sbc (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
long_or_inplace .proc
; long pointed to by AY OR= long pointed to by P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
long_and_inplace .proc
; long pointed to by AY AND= long pointed to by P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
and (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
and (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
and (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
and (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
long_xor_inplace .proc
; long pointed to by AY XOR= long pointed to by P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
eor (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
eor (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W2),y
eor (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts
.pend
long_shiftleftX_inplace .proc
; bit shift left X bits the long value pointed to by AY
cpx #0
beq _end
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
- ldy #0
lda (P8ZP_SCRATCH_W1),y
asl a
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
iny
lda (P8ZP_SCRATCH_W1),y
rol a
sta (P8ZP_SCRATCH_W1),y
dex
bne -
_end rts
.pend
long_shiftrightX_inplace .proc
; bit shift right X bits the long value pointed to by AY
cpx #0
beq _end
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
- ldy #3
lda (P8ZP_SCRATCH_W1),y
asl a ; save sign bit
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
dey
lda (P8ZP_SCRATCH_W1),y
ror a
sta (P8ZP_SCRATCH_W1),y
dex
bne -
_end rts
.pend
@@ -109,6 +109,12 @@ txt {
}}
}
sub print_ulhex(long value, bool prefix) {
if prefix
cbm.CHROUT('$')
print(conv.str_ulhex(value))
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
@@ -165,6 +171,11 @@ _allzero lda #'0'
}}
}
sub print_l(long value) {
; prints a 32 bit value to the screen
print(conv.str_l(value))
}
asmsub input_chars (^^ubyte buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
+39 -6
View File
@@ -102,7 +102,7 @@ sub str_uwbin (uword value) -> str {
}
sub str_uwhex (uword value) -> str {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
; ---- convert the uword in hexadecimal string form (4 digits)
ubyte bits = msb(value)
string_out[0] = hex_digits[bits>>4]
string_out[1] = hex_digits[bits&15]
@@ -113,6 +113,26 @@ sub str_uwhex (uword value) -> str {
return string_out
}
sub str_ulhex (long value) -> str {
; ---- convert the long in hexadecimal string form (8 digits)
uword upperw = msw(value)
uword lowerw = lsw(value)
ubyte bits = msb(upperw)
string_out[0] = hex_digits[bits>>4]
string_out[1] = hex_digits[bits&15]
bits = lsb(upperw)
string_out[2] = hex_digits[bits>>4]
string_out[3] = hex_digits[bits&15]
bits = msb(lowerw)
string_out[4] = hex_digits[bits>>4]
string_out[5] = hex_digits[bits&15]
bits = lsb(lowerw)
string_out[6] = hex_digits[bits>>4]
string_out[7] = hex_digits[bits&15]
string_out[8] = 0
return string_out
}
sub str_uw0 (uword value) -> str {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
uword value2 = value/10
@@ -141,7 +161,7 @@ sub str_uw (uword value) -> str {
}
sub str_w (word value) -> str {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
; ---- convert the (signed) word into decimal string form, without left padding 0's
^^ubyte out_ptr = &string_out
if value<0 {
@(out_ptr) = '-'
@@ -152,6 +172,16 @@ sub str_w (word value) -> str {
return string_out
}
sub str_l (long value) -> str {
; ---- convert the (signed) long into decimal string form, without left padding 0's
%ir {{
loadm.l r99200,conv.str_l.value
load.w r99000,conv.string_out
syscall 60 (r99200.l, r99000.w) : r99000.w
returnr.w r99000
}}
}
sub internal_str_uw(uword value, str out_ptr) {
uword value2 = value/10
ubyte digits = value-value2*10 as ubyte
@@ -267,12 +297,15 @@ sub bin2uword(str string) -> uword {
}
}
sub any2uword(str string) -> uword {
sub any2uword(str string) -> uword, ubyte {
; -- convert any number string (any prefix allowed) to uword.
; returns the parsed word value, and the number of processed characters (including the prefix symbol)
ubyte length
while string[length]!=0 length++
when string[0] {
'$' -> return hex2uword(string)
'%' -> return bin2uword(string)
else -> return str2uword(string)
'$' -> return hex2uword(string), length
'%' -> return bin2uword(string), length
else -> return str2uword(string), length
}
}
+12 -7
View File
@@ -304,6 +304,12 @@ math {
}}
}
sub mul32(uword a, uword b) -> long {
; return 32 bits result of a*b
cx16.r2 = a*b
return mklong2(mul16_last_upper(), cx16.r2)
}
sub diff(ubyte b1, ubyte b2) -> ubyte {
if b1>b2
return b1-b2
@@ -353,10 +359,9 @@ math {
return cx16.r15
}
sub crc32(^^ubyte data, uword length) {
sub crc32(^^ubyte data, uword length) -> long {
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer.
; because prog8 doesn't have 32 bits integers, we have to split up the calculation over 2 words.
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
; Clobbers R12 through R15, returns the result in R0:R1
; There are also "streaming" crc32_start/update/end routines below, that allow you to calculate crc32 for data that doesn't fit in a single memory block.
crc32_start()
cx16.r12 = data
@@ -365,7 +370,7 @@ math {
crc32_update(@(cx16.r12))
cx16.r12++
}
crc32_end()
return crc32_end()
}
sub crc32_start() {
@@ -391,11 +396,11 @@ math {
}
}
sub crc32_end() {
; finalize the "streaming" crc32
; result stored in cx16.r14 (low word) and cx16.r15 (high word)
sub crc32_end() -> long {
; finalize the "streaming" crc32 and return the result in R0:R1
cx16.r15 ^= $ffff
cx16.r14 ^= $ffff
return mklong2(cx16.r15, cx16.r14)
}
; there's no crc32_end_result() here because IR cannot return multiple values yet
+21
View File
@@ -192,6 +192,14 @@ sys {
}}
}
sub pushl(long l) {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
loadm.l r99200,sys.pushl.l
push.l r99200
}}
}
sub push_returnaddress(uword w) {
; note: this actually doesn't do anything useful on the VM because the code execution doesn't use the simulated cpu stack
%ir {{
@@ -222,6 +230,14 @@ sys {
}}
}
sub popl() -> long {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
pop.l r99200
returnr.l r99200
}}
}
sub read_flags() -> ubyte {
; "simulate" the 6502 status register a little bit
if_neg {
@@ -244,6 +260,11 @@ sys {
return cx16.r0L
}
sub cpu_is_65816() -> bool {
; Returns true when you have a 65816 cpu, false when it's a 6502.
return false
}
}
cx16 {
+15
View File
@@ -136,6 +136,13 @@ sub print_uwhex (uword value, bool prefix) {
print(conv.str_uwhex(value))
}
sub print_ulhex (long value, bool prefix) {
; ---- print the ulong in hexadecimal form (4 digits)
if prefix
chrout('$')
print(conv.str_ulhex(value))
}
sub print_uw0 (uword value) {
; ---- print the uword value in decimal form, with left padding 0s (5 positions total)
print(conv.str_uw0(value))
@@ -151,6 +158,14 @@ sub print_w (word value) {
print(conv.str_w(value))
}
sub print_l (long value) {
; ---- print the (signed) long in decimal form, without left padding 0's
%ir {{
loadm.l r99200,txt.print_l.value
syscall 59 (r99200.l)
}}
}
sub input_chars (str buffer) -> ubyte {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
@@ -5,6 +5,7 @@ import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.code.core.*
import kotlin.math.*
@@ -15,6 +16,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"abs" to ::builtinAbs,
"len" to ::builtinLen,
"sizeof" to ::builtinSizeof,
"offsetof" to ::builtinOffsetof,
"sgn" to ::builtinSgn,
"sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
"sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
@@ -24,6 +26,8 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} },
"msw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 65535).toDouble()} },
"mkword" to ::builtinMkword,
"mklong" to ::builtinMklong,
"mklong2" to ::builtinMklong,
"clamp__ubyte" to ::builtinClampUByte,
"clamp__byte" to ::builtinClampByte,
"clamp__uword" to ::builtinClampUWord,
@@ -32,10 +36,12 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"min__byte" to ::builtinMinByte,
"min__uword" to ::builtinMinUWord,
"min__word" to ::builtinMinWord,
"min__long" to ::builtinMinLong,
"max__ubyte" to ::builtinMaxUByte,
"max__byte" to ::builtinMaxByte,
"max__uword" to ::builtinMaxUWord,
"max__word" to ::builtinMaxWord
"max__word" to ::builtinMaxWord,
"max__long" to ::builtinMaxLong
)
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
@@ -90,6 +96,25 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
else throw SyntaxError("abs requires one integer argument", position)
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, "Struct.field"
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val identifier = (args[0] as? IdentifierReference)?.nameInSource
if(identifier==null || identifier.size<2)
throw CannotEvaluateException("offsetof","argument should be an identifier of the form Struct.field")
val structname = identifier.dropLast(1)
val fieldname = identifier.last()
val struct = args[0].definingScope.lookup(structname) as? StructDecl
if(struct==null)
throw SyntaxError("cannot find struct '$structname'", args[0].position)
val offset = struct.offsetof(fieldname, program.memsizer)
if(offset==null)
throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position)
return NumericLiteral.optimalInteger(offset.toInt(), position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = anything, result type = ubyte or uword
if(args.size!=1)
@@ -182,6 +207,29 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
}
private fun builtinMklong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
when(args.size) {
2 -> {
val constMsw = args[0].constValue(program) ?: throw NotConstArgumentException()
val constLsw = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsw.number.toInt() shl 16) or constLsw.number.toInt()
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
}
4 -> {
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constB2 = args[1].constValue(program) ?: throw NotConstArgumentException()
val constB1 = args[2].constValue(program) ?: throw NotConstArgumentException()
val constLsb = args[3].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 24) or
(constB2.number.toInt() shl 16) or
(constB1.number.toInt() shl 8) or
constLsb.number.toInt()
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
}
else -> throw SyntaxError("mkword requires msw and lsw, or msb,b2,b1,lsb arguments", position)
}
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
@@ -225,6 +273,15 @@ private fun builtinMinUWord(args: List<Expression>, position: Position, program:
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
}
private fun builtinMinLong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("min requires 2 arguments", position)
val val1 = args[0].constValue(program) ?: throw NotConstArgumentException()
val val2 = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = min(val1.number.toInt(), val2.number.toInt())
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
}
private fun builtinMaxByte(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("max requires 2 arguments", position)
@@ -261,6 +318,15 @@ private fun builtinMaxUWord(args: List<Expression>, position: Position, program:
return NumericLiteral(BaseDataType.UWORD, result.toDouble(), position)
}
private fun builtinMaxLong(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2)
throw SyntaxError("max requires 2 arguments", position)
val val1 = args[0].constValue(program) ?: throw NotConstArgumentException()
val val2 = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = max(val1.number.toInt(), val2.number.toInt())
return NumericLiteral(BaseDataType.LONG, result.toDouble(), position)
}
private fun builtinClampUByte(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if(args.size!=3)
throw SyntaxError("clamp requires 3 arguments", position)
+12 -6
View File
@@ -5,6 +5,8 @@ import prog8.ast.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.buildversion.VERSION
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram
import prog8.code.ast.printAst
@@ -176,17 +178,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
println("*********** COMPILER AST END *************\n")
}
var symbolTable: SymbolTable
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
symbolTable = stMaker.make()
postprocessSimplifiedAst(intermediateAst, symbolTable, compilationOptions, args.errors)
args.errors.report()
symbolTable = stMaker.make() // need an updated ST because the postprocessing changes stuff
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff
}
if (args.printAst2) {
@@ -204,6 +210,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
intermediateAst,
symbolTable,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
@@ -450,7 +457,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
return CompilationOptions(
outputType, launcherType,
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale,
compTarget, 0u, 0xffffu
compTarget, VERSION, 0u, 0xffffu
)
}
@@ -493,6 +500,8 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
removeUnusedCode(program, errors,compilerOptions)
program.constantFold(errors, compilerOptions)
for(numCycles in 0..10000) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(errors)
@@ -558,6 +567,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
}
private fun createAssemblyAndAssemble(program: PtProgram,
symbolTable: SymbolTable,
errors: IErrorReporter,
compilerOptions: CompilationOptions,
lastGeneratedLabelSequenceNr: Int
@@ -572,10 +582,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
// need to make a new symboltable here to capture possible changes made by optimization steps performed earlier!
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report()
@@ -82,7 +82,6 @@ internal class AstChecker(private val program: Program,
}
}
checkLongType(identifier)
val stmt = identifier.targetStatement(program.builtinFunctions)
if(stmt==null) {
if(identifier.parent is ArrayIndexedExpression) {
@@ -241,6 +240,11 @@ internal class AstChecker(private val program: Program,
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
}
BaseDataType.LONG -> {
if(!iterableDt.elementType().isInteger)
errors.err("long loop variable can only loop over integers", forLoop.position)
}
BaseDataType.FLOAT -> {
// Looping over float variables is very inefficient because the loopvar is going to
// get copied over with new values all the time. We don't support this for now.
@@ -363,10 +367,6 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
override fun visit(numLiteral: NumericLiteral) {
checkLongType(numLiteral)
}
private fun hasReturnOrExternalJumpOrRts(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
@@ -813,7 +813,6 @@ internal class AstChecker(private val program: Program,
}
override fun visit(addressOf: AddressOf) {
checkLongType(addressOf)
val variable=addressOf.identifier?.targetVarDecl()
if (variable!=null) {
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
@@ -862,8 +861,6 @@ internal class AstChecker(private val program: Program,
if(decl.names.size>1)
throw InternalCompilerException("vardecls with multiple names should have been converted into individual vardecls")
if(decl.datatype.isLong && decl.type!=VarDeclType.CONST)
errors.err("cannot use long type for variables; only for constants", decl.position)
if(decl.type==VarDeclType.MEMORY) {
if (decl.datatype.isString)
errors.err("strings cannot be memory-mapped", decl.position)
@@ -949,6 +946,9 @@ internal class AstChecker(private val program: Program,
dt.isWordArray ->
if(arraySize > 128)
err("regular word array length must be 1-128, use split array to get to 256")
dt.isLongArray ->
if(arraySize > 64)
err("long array length must be 1-64")
dt.isFloatArray ->
if(arraySize > 51)
err("float array length must be 1-51")
@@ -1052,6 +1052,10 @@ internal class AstChecker(private val program: Program,
if (length == 0 || length > 128)
err("regular word array length must be 1-128, use split array to get to 256")
}
decl.datatype.isLongArray -> {
if (length == 0 || length > 64)
err("long array length must be 1-64")
}
decl.datatype.isFloatArray -> {
if (length == 0 || length > 51)
err("float array length must be 1-51")
@@ -1092,7 +1096,7 @@ internal class AstChecker(private val program: Program,
if(decl.datatype.isStructInstance && decl.origin!=VarDeclOrigin.SUBROUTINEPARAM) {
if(decl.type==VarDeclType.MEMORY)
errors.err("cannot declare memory mapped struct instances, use a pointer and memory allocation call or direct address assignment instead", decl.position)
errors.err("struct instances cannot be declared as memory-mapped currently, use a pointer and memory allocation call or direct address assignment instead", decl.position)
else
errors.err("struct instances cannot be declared directly, use a pointer and memory allocation call or direct address assignment instead", decl.position)
}
@@ -1269,17 +1273,23 @@ internal class AstChecker(private val program: Program,
val arrayspec = ArrayIndex.forArray(array)
checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array)
} else {
errors.err("undefined array type (multiple element types?)", array.position)
errors.err("undefined array type (multiple or incompatible element types?)", array.position)
}
if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteral || it is AddressOf || (it is FunctionCallExpression && it.target.targetStructDecl()!=null) }) {
if (!array.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer }) {
errors.err("initialization value contains non-constant elements", array.value[0].position)
}
if(array.value.any { it is FunctionCallExpression }) {
errors.err("it is not yet possible to use struct initializations in an array, you have to do it one by one for now", array.value[0].position)
// TODO this is because later in the simplified AST the allocate struct variable is still missing somehow
val elementDt = (array.parent as VarDecl).datatype.elementType()
if(elementDt.isPointer) {
// all elements in the initializer array should be of the same element type
array.value.forEach {
val valueDt = it.inferType(program).getOrUndef()
if(!valueDt.isUnsignedWord && valueDt != elementDt) {
errors.err("struct initializer element has invalid type, expected $elementDt or uword but got $valueDt", it.position)
}
}
}
} else if(array.parent is ForLoop) {
@@ -1323,7 +1333,6 @@ internal class AstChecker(private val program: Program,
}
}
checkLongType(expr)
val dt = expr.expression.inferType(program).getOrUndef()
if(!dt.isUndefined) {
@@ -1468,8 +1477,6 @@ internal class AstChecker(private val program: Program,
return
}
checkLongType(expr)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) {
@@ -1515,7 +1522,7 @@ internal class AstChecker(private val program: Program,
if(rightDt.isWord) {
val shift = expr.right.constValue(program)?.number?.toInt()
if(shift==null || shift > 255) {
errors.err("shift by a word value not supported, max is a byte", expr.position)
errors.err("bit shift value can not be larger than a byte", expr.position)
}
}
}
@@ -1546,8 +1553,13 @@ internal class AstChecker(private val program: Program,
errors.err("can only use string repeat with a constant number value", expr.left.position)
} else if(leftDt.isBool && rightDt.isByte || leftDt.isByte && rightDt.isBool) {
// expression with one side BOOL other side (U)BYTE is allowed; bool==byte
} else if((expr.operator == "<<" || expr.operator == ">>") && (leftDt.isWord && rightDt.isByte)) {
// exception allowed: shifting a word by a byte
} else if((expr.operator == "<<" || expr.operator == ">>")
&& (leftDt.isByte && rightDt.isByte ||
leftDt.isWord && rightDt.isByte ||
leftDt.isWord && rightDt.isWord ||
leftDt.isLong && rightDt.isByte ||
leftDt.isLong && rightDt.isWord)) {
// exception allowed: shifting a word by a byte, long by a word or byte
} else if((expr.operator in BitwiseOperators) && (leftDt.isInteger && rightDt.isInteger)) {
// exception allowed: bitwise operations with any integers
} else if((leftDt.isUnsignedWord && rightDt.isString) || (leftDt.isString && rightDt.isUnsignedWord)) {
@@ -1608,7 +1620,6 @@ internal class AstChecker(private val program: Program,
}
override fun visit(typecast: TypecastExpression) {
checkLongType(typecast)
if(typecast.type.isPassByRef)
errors.err("cannot type cast to string or array type", typecast.position)
@@ -1665,7 +1676,6 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallExpr: FunctionCallExpression) {
checkLongType(functionCallExpr)
// this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
@@ -1898,36 +1908,7 @@ internal class AstChecker(private val program: Program,
}
}
if(target is StructDecl) {
// it's a static struct inializer, check the values
if(args.isNotEmpty()) {
args.forEach {
if(it is IdentifierReference) {
val target = it.targetVarDecl()
if(target!=null && target.datatype.isPointer) {
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
}
}
}
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
errors.err("initialization value contains non-constant elements", args[0].position)
if (target.fields.size != args.size)
errors.err("initialization value needs to have same number of values as the struct has fields, or be empty: expected ${target.fields.size} or 0, got ${args.size}", args[0].position)
else
target.fields.zip(args).withIndex().forEach { (index, fv) ->
val (field, value) = fv
val valueDt = value.inferType(program)
if(valueDt isNotAssignableTo field.first) {
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
}
}
}
// TODO rest?
}
args.forEach{
checkLongType(it)
if(it.inferType(program).isStructInstance)
errors.err("structs can only be passed via a pointer", it.position)
}
@@ -1940,7 +1921,6 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
checkLongType(arrayIndexedExpression)
val target = arrayIndexedExpression.plainarrayvar?.targetStatement(program.builtinFunctions)
if(target is VarDecl) {
if (!target.datatype.isIterable && !target.datatype.isUnsignedWord && !target.datatype.isPointer)
@@ -1974,7 +1954,10 @@ internal class AstChecker(private val program: Program,
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
}
} else if(target!=null) {
throw FatalAstException("target is not a variable")
if(target is Subroutine || target is Label )
errors.err("cannot array index a subroutine or label directly, use an intermediate uword pointer = &symbol instead", arrayIndexedExpression.position)
else
errors.err("indexed symbol is not a suitable variable", arrayIndexedExpression.position)
}
if(arrayIndexedExpression.pointerderef!=null) {
@@ -2179,17 +2162,8 @@ internal class AstChecker(private val program: Program,
else
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
}
}
private fun checkLongType(expression: Expression) {
if(expression.inferType(program) issimpletype BaseDataType.LONG) {
if((expression.parent as? VarDecl)?.type!=VarDeclType.CONST) {
if (expression.parent !is RepeatLoop) {
if (errors.noErrorForLine(expression.position))
errors.err("integer overflow", expression.position)
}
}
}
super.visit(deref)
}
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
@@ -2282,6 +2256,20 @@ internal class AstChecker(private val program: Program,
}
return err("invalid word array initialization value ${value.type}, expected $targetDt")
}
targetDt.isPointerArray -> {
val arraySpecSize = arrayspec.constIndex()
val arraySize = value.value.size
val maxLength = if(targetDt.isSplitWordArray) 256 else 128
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize>maxLength)
return err("array length must be 1-$maxLength")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid array size, must be 1-$maxLength")
}
targetDt.isFloatArray -> {
// value may be either a single float, or a float arraysize
if(value.type istype targetDt) {
@@ -2396,6 +2384,7 @@ internal class AstChecker(private val program: Program,
else
cast.valueOrZero().number
}
is StaticStructInitializer -> it.structname.hashCode() and 0xffff
else -> -9999999
}
}
@@ -2420,7 +2409,7 @@ internal class AstChecker(private val program: Program,
else -> throw FatalAstException("invalid type $targetDt")
}
if (!correct) {
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf })
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer })
errors.err("initialization value contains non-constant elements", value.value[0].position)
else
errors.err("array element out of range for type $targetDt", value.position)
@@ -2481,7 +2470,7 @@ internal class AstChecker(private val program: Program,
else if (targetDatatype.isPointer) {
if(sourceDatatype.isPointer) {
if(!(sourceDatatype isAssignableTo targetDatatype))
errors.err("cannot assign different pointer type, expected $targetDatatype got $sourceDatatype", position)
errors.err("cannot assign different pointer type, expected $targetDatatype or uword but got $sourceDatatype", position)
} else if(sourceDatatype.isString && targetDatatype.sub?.isByte==true) {
// assigning a string to a byte pointer is allowed.
} else if(!sourceDatatype.isUnsignedWord && !sourceDatatype.isStructInstance)
@@ -2512,11 +2501,40 @@ internal class AstChecker(private val program: Program,
}
override fun visit(deref: ArrayIndexedPtrDereference) {
if(deref.parent is AssignTarget)
errors.err("no support for assigning to a array indexed pointer target like this yet. Split the assignment statement by using an intermediate variable.", deref.position)
else
if(deref.parent !is AssignTarget)
errors.err("no support for getting the target value of pointer array indexing like this yet. Split the expression by using an intermediate variable.", deref.position) // this may never occur anymore since more ArrayIndexedPtrDereference got rewritten
super.visit(deref)
}
override fun visit(initializer: StaticStructInitializer) {
val args = initializer.args
if(args.isNotEmpty()) {
args.forEach {
if(it is IdentifierReference) {
val target = it.targetVarDecl()
if(target!=null && target.datatype.isPointer) {
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
}
}
}
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
errors.err("initialization value contains non-constant elements", args[0].position)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
require(args.size==struct.fields.size)
struct.fields.zip(args).withIndex().forEach { (index, fv) ->
val (field, value) = fv
val valueDt = value.inferType(program)
if(valueDt isNotAssignableTo field.first)
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
}
}
}
super.visit(initializer)
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {
@@ -165,14 +165,23 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
super.visit(string)
}
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
override fun visit(functionCallExpr: FunctionCallExpression) {
visitFunctionCall(functionCallExpr)
super.visit(functionCallExpr)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
visitFunctionCall(functionCallStatement)
super.visit(functionCallStatement)
}
override fun visit(initializer: StaticStructInitializer) {
val fields = initializer.structname.targetStructDecl()!!.fields
if(initializer.args.isNotEmpty() && initializer.args.size != fields.size) {
val pos = (if(initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, fields.map { it.second }, true)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
if (initializer.args.isNotEmpty() && initializer.args.size != struct.fields.size) {
val pos = (if (initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, struct.fields.map { it.second }, true)
}
}
}
@@ -174,6 +174,8 @@ class AstPreprocessor(val program: Program,
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
if(expr.operator=="in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
@@ -267,7 +269,7 @@ class AstPreprocessor(val program: Program,
.filter { it.name !in namesInSub && it.name !in existingAliases }
.forEach {
if (it.registerOrPair in Cx16VirtualRegisters) {
if(it.type.isIntegerOrBool) {
if(it.type.isWordOrByteOrBool) {
val mappedParamVar = VarDecl.fromParameter(it)
mods += IAstModification.InsertFirst(mappedParamVar, subroutine)
} else {
@@ -153,6 +153,8 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
if (options.compTarget.name == VMTarget.NAME)
return noModifications
@@ -79,18 +79,26 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
val (replaced, cast) = arg1.typecastTo(if(dt1.isUnsignedByte) BaseDataType.UWORD else BaseDataType.WORD, dt1, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
} else {
} else if(dt1.isWord) {
if(dt2.isWord)
return noModifications
val (replaced, cast) = arg2.typecastTo(if(dt2.isUnsignedByte) BaseDataType.UWORD else BaseDataType.WORD, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
} else if(dt1.isLong) {
if(dt2.isLong)
return noModifications
val (replaced, cast) = arg2.typecastTo(BaseDataType.LONG, dt2, true)
if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)
if(shifts!=null) {
@@ -605,11 +605,15 @@ _after:
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
mutableListOf(ifSt)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
mutableListOf(conditionVar, assignIndex!!, ifSt)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
val declscope = parent.definingScope
return listOf(
IAstModification.ReplaceNode(ongoto, replacementScope, parent),
IAstModification.InsertFirst(jumplistArray, declscope)
)
}
override fun after(deref: PtrDereference, parent: Node): Iterable<IAstModification> {
@@ -658,6 +662,61 @@ _after:
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
// get rid of the ArrayIndexedPtrDereference AST node, replace it with other AST nodes that are equivalent
fun pokeFunc(dt: DataType): Pair<String, DataType?> {
return when {
dt.isBool -> "pokebool" to null
dt.isUnsignedByte -> "poke" to null
dt.isSignedByte -> "poke" to DataType.UBYTE
dt.isUnsignedWord -> "pokew" to null
dt.isSignedWord -> "pokew" to DataType.UWORD
dt.isLong -> "pokel" to null
dt.isFloat -> "pokef" to null
else -> throw FatalAstException("can only deref a numeric or boolean pointer here")
}
}
if(parent is AssignTarget) {
if(!deref.derefLast) {
val assignment = parent.parent as Assignment
val field = deref.chain.last()
val ptr = deref.chain.dropLast(1)
if(field.second==null && ptr.last().second!=null) {
val ptrName = ptr.map { it.first }
val ptrVar = deref.definingScope.lookup(ptrName) as? VarDecl
if(ptrVar!=null && (ptrVar.datatype.isPointer || ptrVar.datatype.isPointerArray)) {
val struct = ptrVar.datatype.subType!! as StructDecl
val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.memsizer)!!.toInt(), deref.position)
val pointerIdentifier = IdentifierReference(ptrName, deref.position)
val address: Expression
if(ptrVar.datatype.isPointer) {
// pointer[idx].field = value --> pokeXXX(pointer as uword + idx*sizeof(Struct) + offsetof(Struct.field), value)
val structSize = ptrVar.datatype.dereference().size(program.memsizer)
val pointerAsUword = TypecastExpression(pointerIdentifier, DataType.UWORD, true, deref.position)
val idx = ptr.last().second!!.indexExpr
val scaledIndex = BinaryExpression(idx, "*", NumericLiteral(BaseDataType.UWORD, structSize.toDouble(), deref.position), deref.position)
val structAddr = BinaryExpression(pointerAsUword, "+", scaledIndex, deref.position)
address = BinaryExpression(structAddr, "+", offsetNumber, deref.position)
}
else {
// pointerarray[idx].field = value --> pokeXXX(pointerarray[idx] as uword + offsetof(Struct.field), value)
val index = ArrayIndexedExpression(pointerIdentifier, null, ptr.last().second!!, deref.position)
val pointerAsUword = TypecastExpression(index, DataType.UWORD, true, deref.position)
address = BinaryExpression(pointerAsUword, "+", offsetNumber, deref.position)
}
val (pokeFunc, valueCast) = pokeFunc(parent.inferType(program).getOrUndef())
val value = if(valueCast==null) assignment.value else TypecastExpression(assignment.value, valueCast, true, assignment.value.position)
val pokeCall = FunctionCallStatement(IdentifierReference(listOf(pokeFunc), assignment.position),
mutableListOf(address, value), false, assignment.position)
if(assignment.isAugmentable)
errors.warn("in-place assignment of indexed pointer variable currently is very inefficient, maybe use a temporary pointer variable", assignment.position)
return listOf(IAstModification.ReplaceNode(assignment, pokeCall, assignment.parent))
}
}
}
}
if(deref.chain.last().second!=null && deref.derefLast && deref.chain.dropLast(1).all { it.second==null } ) {
// parent could be Assigment directly, or a binexpr chained pointer expression (with '.' operator)
@@ -710,15 +769,7 @@ _after:
val dt = deref.inferType(program).getOrUndef()
if(dt.isNumericOrBool) {
// if it's something else beside number (like, a struct instance) we don't support rewriting that...
val (pokeFunc, cast) =
if (dt.isBool) "pokebool" to null
else if (dt.isUnsignedByte) "poke" to null
else if (dt.isSignedByte) "poke" to DataType.UBYTE
else if (dt.isUnsignedWord) "pokew" to null
else if (dt.isSignedWord) "pokew" to DataType.UWORD
else if (dt.isLong) "pokel" to null
else if (dt.isFloat) "pokef" to null
else throw FatalAstException("can only deref a numeric or boolean pointer here")
val (pokeFunc, cast) = pokeFunc(dt)
val indexer = deref.chain.last().second!!
val identifier = IdentifierReference(deref.chain.map { it.first }, deref.position)
val indexed = ArrayIndexedExpression(identifier, null, indexer, deref.position)
@@ -872,4 +923,68 @@ _after:
errors.err(error, ifExpr.condition.position)
return noModifications
}
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
fun convertArrayIntoStructInitializer(array: ArrayLiteral, struct: ISubType): StaticStructInitializer {
val structname = IdentifierReference(struct.scopedNameString.split("."), array.position)
return StaticStructInitializer(structname, array.value.toMutableList(), array.position)
}
fun checkNumberOfElements(struct: StructDecl, array: ArrayLiteral): Boolean {
val numValues = array.value.size
if (numValues>0 && struct.fields.size != numValues) {
if (numValues < struct.fields.size) {
val missing = struct.fields.drop(numValues).joinToString(", ") { it.second }
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}, missing: $missing", array.position)
} else
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}", array.position)
return false
}
return true
}
if(parent is VarDecl) {
if (parent.datatype.isPointerArray && parent.datatype.elementType().subType!=null) {
val struct = parent.datatype.elementType().subType as StructDecl
val allremovals = mutableListOf<VarDecl>()
var noErrors = true
var changes = false
array.value.withIndex().forEach { (index, elt) ->
if(elt is ArrayLiteral) {
noErrors = noErrors and checkNumberOfElements(struct, elt)
if(noErrors) {
array.value[index] = convertArrayIntoStructInitializer(elt, struct)
changes = true
}
} else if(elt is IdentifierReference) {
val arrayvar = elt.targetVarDecl()!!.value as ArrayLiteral
noErrors = noErrors and checkNumberOfElements(struct, arrayvar)
if(noErrors) {
array.value[index] = convertArrayIntoStructInitializer(arrayvar, struct)
allremovals += elt.targetVarDecl()!!
changes = true
}
}
}
if(changes && noErrors) {
array.linkParents(parent)
return allremovals.map { IAstModification.Remove(it, it.parent as IStatementContainer) }
}
}
}
else if(parent is Assignment) {
val targetDt = parent.target.inferType(program).getOrUndef()
if(targetDt.isPointer && targetDt.subType!=null) {
val struct = targetDt.subType as StructDecl
if(checkNumberOfElements(struct, array)) {
val initializser = convertArrayIntoStructInitializer(array, struct)
return listOf(IAstModification.ReplaceNode(array, initializser, parent))
}
}
}
return noModifications
}
}
@@ -270,7 +270,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
srcTarget.pointerDereference!=null -> target.add(transform(srcTarget.pointerDereference!!))
srcTarget.arrayIndexedDereference!=null -> TODO("this array indexed dereference should have been converted to some other ast nodes ${srcTarget.position}")
srcTarget.arrayIndexedDereference!=null -> throw FatalAstException("this should have been converted to some other ast nodes ${srcTarget.position}")
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
}
return target
@@ -21,6 +21,8 @@ internal fun postprocessSimplifiedAst(
private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) {
fun getStStruct(subType: ISubType): StStruct {
if(subType is StStruct)
return subType
val stNode = st.lookup(subType.scopedNameString) as? StStruct
if(stNode != null)
return stNode
@@ -28,7 +30,7 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
throw FatalAstException("cannot find in ST: ${subType.scopedNameString} $subType")
}
fun fixSubtype(type: DataType) {
fun fixSubtypeIntoStType(type: DataType) {
if(type.subType!=null && type.subType !is StStruct) {
type.subType = getStStruct(type.subType!!)
}
@@ -36,13 +38,21 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
fun fixSubtypes(node: PtNode) {
when(node) {
is IPtVariable -> fixSubtype(node.type)
is PtPointerDeref -> fixSubtype(node.type)
is PtStructDecl -> node.fields.forEach { fixSubtype(it.first) }
is PtAsmSub -> node.returns.forEach { fixSubtype(it.second) }
is PtExpression -> fixSubtype(node.type)
is PtSubSignature -> node.returns.forEach { fixSubtype(it) }
is PtSubroutineParameter -> fixSubtype(node.type)
is IPtVariable -> {
fixSubtypeIntoStType(node.type)
// if it's an array, fix the subtypes of its elements as well
if(node.type.isArray && node is PtVariable) {
(node.value as? PtArray)?.let {array ->
array.children.forEach { fixSubtypes(it) }
}
}
}
is PtPointerDeref -> fixSubtypeIntoStType(node.type)
is PtStructDecl -> node.fields.forEach { fixSubtypeIntoStType(it.first) }
is PtAsmSub -> node.returns.forEach { fixSubtypeIntoStType(it.second) }
is PtExpression -> fixSubtypeIntoStType(node.type)
is PtSubSignature -> node.returns.forEach { fixSubtypeIntoStType(it) }
is PtSubroutineParameter -> fixSubtypeIntoStType(node.type)
else -> { /* has no datatype */ }
}
}
@@ -162,7 +172,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtAddressOf -> value.arrayIndexExpr == null || notComplex(value.arrayIndexExpr!!)
is PtBuiltinFunctionCall -> {
when (value.name) {
in arrayOf("msb", "lsb", "msw", "lsw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> value.args.all { notComplex(it) }
in arrayOf("msb", "lsb", "msw", "lsw", "mkword", "mklong", "mklong2", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> value.args.all { notComplex(it) }
else -> false
}
}
@@ -175,7 +185,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
is PtNumber,
is PtRange,
is PtString -> true
is PtIdentifier -> true // actually PtIdentifier IS "complex" this time (it's a variable that might change) but it's kinda annoying to give a warning message for this very common case
// note that PtIdentifier als is "complex" this time (it's a variable that might change)
else -> false
}
@@ -192,7 +202,6 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
}
// complex return value, need to store it before calling the defer block
errors.warn("using defer with nontrivial return value(s) incurs stack overhead", ret.children.first { !notComplex(it as PtExpression)}.position)
val pushAndPopCalls = ret.children.map { makePushPopFunctionCalls(it as PtExpression) }
val pushCalls = pushAndPopCalls.map { it.first }.reversed() // push in reverse order
val popCalls = pushAndPopCalls.map { it.second }
@@ -384,4 +384,25 @@ internal class StatementReorderer(
)
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
}
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
if(parent is AssignTarget) {
val zeroIndexer = deref.chain.firstOrNull { it.second?.constIndex()==0 }
if(zeroIndexer!=null) {
val target = deref.definingScope.lookup(listOf(zeroIndexer.first))
if(target is VarDecl && target.datatype.isPointer) {
val position = deref.chain.indexOf(zeroIndexer)
val rest = deref.chain.drop(position + 1)
if (rest.size == 1 && rest[0].second == null) {
// pointer[0]^^.field = xxx --> pointer^^.field = xxx
val noindexer = zeroIndexer.first to null
val newchain = deref.chain.take(position) + noindexer + rest
val newDeref = PtrDereference(newchain.map { it.first }, false, deref.position)
return listOf(IAstModification.ReplaceNode(deref, newDeref, parent))
}
}
}
}
return noModifications
}
}
@@ -79,6 +79,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator==".")
return noModifications
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
val leftCv = expr.left.constValue(program)
@@ -170,7 +173,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isWords || !rightDt.isBytes) {
if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isInteger || !rightDt.isBytes) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOrUndef(), rightDt.getOrUndef(), expr.left, expr.right)
if(toFix!=null) {
@@ -627,9 +630,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return if(initializer.args.isEmpty())
noModifications
else {
val struct = initializer.structname.targetStructDecl()!!
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
}
else noModifications
}
}
@@ -12,6 +12,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
var pushTypecast: DataType? = null
var pushWord = false
var pushFloat = false
var pushLong = false
when {
value.type.isBool -> {
@@ -29,13 +30,14 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
}
value.type.isUnsignedByte -> {}
value.type.isUnsignedWord -> pushWord = true
value.type.isLong -> pushLong = true
value.type.isPassByRef -> pushWord = true
value.type.isFloat -> pushFloat = true
else -> throw FatalAstException("unsupported return value type ${value.type} with defer")
}
val pushFunc = if(pushFloat) "floats.push" else if(pushWord) "sys.pushw" else "sys.push"
val popFunc = if(pushFloat) "floats.pop" else if(pushWord) "sys.popw" else "sys.pop"
val pushFunc = if(pushFloat) "floats.push" else if(pushWord) "sys.pushw" else if (pushLong) "sys.pushl" else "sys.push"
val popFunc = if(pushFloat) "floats.pop" else if(pushWord) "sys.popw" else if(pushLong) "sys.popl" else "sys.pop"
val pushCall = PtFunctionCall(pushFunc, true, DataType.UNDEFINED, value.position)
if(pushTypecast!=null) {
val typecast = PtTypeCast(pushTypecast, value.position).also {
@@ -47,7 +49,7 @@ internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall,
}
val popCall = if(popTypecast!=null) {
PtTypeCast(popTypecast, value.position).also {
val returnDt = if(pushWord) DataType.UWORD else DataType.UBYTE
val returnDt = if(pushWord) DataType.UWORD else if(pushLong) DataType.LONG else if(pushFloat) DataType.FLOAT else DataType.UBYTE
it.add(PtFunctionCall(popFunc, false, returnDt, value.position))
}
} else
@@ -49,9 +49,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
val constValue = decl.value!!.constValue(program)!!
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
// don't make it signed if it was unsigned and vice versa
if(valueDt.isSigned && decl.datatype.isUnsigned ||
valueDt.isUnsigned && decl.datatype.isSigned) {
// don't make it signed if it was unsigned and vice versa, except when it is a long const declaration
if(!decl.datatype.isLong &&
(valueDt.isSigned && decl.datatype.isUnsigned ||
valueDt.isUnsigned && decl.datatype.isSigned))
{
val constValue = decl.value!!.constValue(program)!!
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
@@ -124,8 +126,12 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
else {
if(scope.statements.any {it is VarDecl}) {
throw FatalAstException("there are leftover vardecls in the nested scope at ${scope.position}, they should have been moved/placed in the declaration scope (subroutine) by now")
}
noModifications
}
}
private class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
+16 -14
View File
@@ -10,6 +10,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.IErrorReporter
import prog8.code.source.SourceCode
import prog8.compiler.ModuleImporter
@@ -49,7 +50,7 @@ class TestModuleImporter: FunSpec({
withClue(".file should point to specified path") {
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { error("should have import error") }
withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}"
@@ -57,7 +58,7 @@ class TestModuleImporter: FunSpec({
withClue(".file should point to specified path") {
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
test("testDirectory") {
@@ -75,7 +76,7 @@ class TestModuleImporter: FunSpec({
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
.let {
@@ -86,7 +87,7 @@ class TestModuleImporter: FunSpec({
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -101,7 +102,7 @@ class TestModuleImporter: FunSpec({
val path = assumeReadableFile(searchIn[0], fileName)
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -118,7 +119,7 @@ class TestModuleImporter: FunSpec({
}
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -131,7 +132,7 @@ class TestModuleImporter: FunSpec({
assumeReadableFile(searchIn, path)
val module = importer.importMainModule(path).getOrElse { throw it }
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
module shouldBeIn program.modules
module.program shouldBe program
}
@@ -152,7 +153,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -170,9 +171,10 @@ class TestModuleImporter: FunSpec({
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
}
}
@@ -203,14 +205,14 @@ class TestModuleImporter: FunSpec({
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
errors.report()
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
val result2 = importer.importImplicitLibraryModule(filenameWithExt)
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
errors.report()
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
}
@@ -232,7 +234,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
}
}
@@ -254,7 +256,7 @@ class TestModuleImporter: FunSpec({
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
importer.errors.report()
}
+4
View File
@@ -97,6 +97,7 @@ class TestCompilerOnExamplesC64: FunSpec({
"library/main",
"plasma",
"rasterbars",
"simplemultiplexer",
"sprites",
"starfield",
"tehtriz",
@@ -149,6 +150,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"bdmusic",
"bobs",
"bubbleuniverse",
"charfade",
"charsets",
"circles",
"cobramk3-gfx",
@@ -223,6 +225,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"textelite",
"pointers/animalgame",
"pointers/binarytree",
"pointers/hashtable",
"pointers/sortedlist",
"pointers/sorting"
),
@@ -253,6 +256,7 @@ class TestCompilerOnExamplesVirtual: FunSpec({
"sincos",
"pointers/animalgame",
"pointers/binarytree",
"pointers/hashtable",
"pointers/sortedlist",
"pointers/fountain-virtual",
"pointers/sorting"
+1
View File
@@ -21,6 +21,7 @@ class TestGoldenRam: FunSpec({
noSysInit = false,
romable = false,
compTarget = VMTarget(),
compilerVersion="99.99",
loadAddress = 999u,
memtopAddress = 0xffffu
)
+1 -1
View File
@@ -12,7 +12,7 @@ class TestLaunchEmu: FunSpec({
val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test">
<PROGRAM NAME="test" COMPILERVERSION="99.99">
<OPTIONS>
</OPTIONS>
+2 -3
View File
@@ -386,9 +386,6 @@ class TestMemory: FunSpec({
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.UNDEFINED)
}
shouldThrow<NoSuchElementException> {
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10)
}
target.memorySize(BaseDataType.BOOL) shouldBe 1
target.memorySize(BaseDataType.BYTE) shouldBe 1
target.memorySize(BaseDataType.WORD) shouldBe 2
@@ -397,6 +394,7 @@ class TestMemory: FunSpec({
target.memorySize(DataType.BOOL, null) shouldBe 1
target.memorySize(DataType.WORD, null) shouldBe 2
target.memorySize(DataType.LONG, null) shouldBe 4
target.memorySize(DataType.FLOAT, null) shouldBe target.FLOAT_MEM_SIZE
target.memorySize(DataType.STR, null) shouldBe 2
@@ -409,6 +407,7 @@ class TestMemory: FunSpec({
target.memorySize(DataType.arrayFor(BaseDataType.BYTE), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10) shouldBe 40
target.memorySize(DataType.arrayFor(BaseDataType.FLOAT), 10) shouldBe 10*target.FLOAT_MEM_SIZE
target.memorySize(DataType.arrayFor(BaseDataType.WORD, true), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD, true), 10) shouldBe 20
+194 -145
View File
@@ -3,7 +3,6 @@ package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@@ -764,7 +763,6 @@ main {
test("address-of struct fields") {
val src="""
%import floats
%import textio
main {
struct List {
@@ -1027,118 +1025,6 @@ main {
st[8] shouldBe instanceOf<Assignment>()
}
test("indexing pointers with index 0 is just a direct pointer dereference except when followed by a struct field lookup") {
val src="""
%import floats
main {
struct List {
^^uword s
ubyte n
}
sub start() {
^^List l1 = ^^List : []
^^word @shared wptr
^^float @shared fptr
float f1,f2
cx16.r0 = l1.s^^
cx16.r1 = l1^^.s^^
cx16.r2 = l1.s^^
cx16.r3 = l1.s[0]
cx16.r4 = l1^^.s[0]
l1.s^^ = 4242
l1^^.s^^ = 4242
l1.s^^ = 4242
l1.s[0] = 4242
;; l1^^.s[0] = 4242 ; TODO fix parse syntax error
cx16.r0s = wptr[0]
cx16.r1s = wptr^^
wptr^^ = 4242
wptr[0] = 4242
f1 = fptr^^
f2 = fptr[0]
fptr^^ = 1.234
fptr[0] = 1.234
; not changed to dereference:
cx16.r0L = l1[0].n
cx16.r1L = l1[1].n
}
}"""
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 30
val dr0 = (st[10] as Assignment).value as PtrDereference
val dr1 = (st[11] as Assignment).value as PtrDereference
val dr2 = (st[12] as Assignment).value as PtrDereference
val dr3 = (st[13] as Assignment).value as PtrDereference
val dr4 = (st[14] as Assignment).value as PtrDereference
val dr5 = (st[15] as Assignment).target.pointerDereference!!
val dr6 = (st[16] as Assignment).target.pointerDereference!!
val dr7 = (st[17] as Assignment).target.pointerDereference!!
val dr8 = (st[18] as Assignment).target.pointerDereference!!
val dr9 = (st[19] as Assignment).value as FunctionCallExpression
val dr10 = (st[20] as Assignment).value as PtrDereference
val dr11 = (st[21] as Assignment).target.pointerDereference!!
(st[22] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokew")
val dr13 = (st[23] as Assignment).value as PtrDereference
((st[24] as Assignment).value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekf")
val dr15 = (st[25] as Assignment).target.pointerDereference!!
(st[26] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokef")
dr0.chain shouldBe listOf("l1", "s")
dr0.derefLast shouldBe true
dr1.chain shouldBe listOf("l1", "s")
dr1.derefLast shouldBe true
dr2.chain shouldBe listOf("l1", "s")
dr2.derefLast shouldBe true
dr3.chain shouldBe listOf("l1", "s")
dr3.derefLast shouldBe true
dr4.chain shouldBe listOf("l1", "s")
dr4.derefLast shouldBe true
dr5.chain shouldBe listOf("l1", "s")
dr5.derefLast shouldBe true
dr6.chain shouldBe listOf("l1", "s")
dr6.derefLast shouldBe true
dr7.chain shouldBe listOf("l1", "s")
dr7.derefLast shouldBe true
dr8.chain shouldBe listOf("l1", "s")
dr8.derefLast shouldBe true
dr9.target.nameInSource shouldBe listOf("peekw")
dr10.chain shouldBe listOf("wptr")
dr10.derefLast shouldBe true
dr11.chain shouldBe listOf("wptr")
dr11.derefLast shouldBe true
dr13.chain shouldBe listOf("fptr")
dr13.derefLast shouldBe true
dr15.chain shouldBe listOf("fptr")
dr15.derefLast shouldBe true
val list0 = (st[27] as Assignment).value as BinaryExpression
val list1 = (st[28] as Assignment).value as BinaryExpression
list0.operator shouldBe "."
(list0.right as IdentifierReference).nameInSource shouldBe listOf("n")
val list0left = list0.left as ArrayIndexedExpression
list0left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
list0left.indexer.constIndex() shouldBe 0
list1.operator shouldBe "."
(list1.right as IdentifierReference).nameInSource shouldBe listOf("n")
val list1left = list0.left as ArrayIndexedExpression
list1left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
list1left.indexer.constIndex() shouldBe 0
}
test("indexing pointers to structs") {
val src="""
%import floats
@@ -1458,33 +1344,32 @@ other {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("a.b.c[i]^^.value = X where pointer is struct gives good error message") {
test("support for assigning to indexed pointers") {
val src="""
main {
sub start() {
other.foo.listarray[3]^^.value = cx16.r0
other.foo()
}
}
sprptr[2]^^.y = 99
pokew(sprptr as uword + (sizeof(Sprite) as uword)*2 + offsetof(Sprite.y), 99)
sprptr[cx16.r0L]^^.y = 99
pokew(sprptr as uword + (sizeof(Sprite) as uword)*cx16.r0L + offsetof(Sprite.y), 99)
other {
sub foo() {
struct List {
bool b
uword value
}
^^List[10] listarray
listarray[3]^^.value = cx16.r0
listarray[3]^^ = 999 ; cannot assign word value to struct instance
sprites[2]^^.y = 99
pokew(sprites[2] as uword + offsetof(Sprite.y), 99)
sprites[cx16.r0L]^^.y = 99
pokew(sprites[cx16.r0L] as uword + offsetof(Sprite.y), 99)
}
struct Sprite {
ubyte x
uword y
}
^^Sprite[4] @shared sprites
^^Sprite @shared sprptr
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBeGreaterThanOrEqualTo 3
errors.errors[0] shouldContain "no support for"
errors.errors[1] shouldContain "no support for"
errors.errors[2] shouldContain "assigning this value to struct instance not supported"
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
xtest("array indexed assignment parses with and without explicit dereference after struct pointer [IGNORED because it's a parser error right now]") {
@@ -1572,7 +1457,6 @@ other {
test("passing arrays to subroutines via typed pointer parameters") {
val src="""
%import floats
%import textio
main {
struct Node {
@@ -1731,7 +1615,34 @@ main {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("type error for invalid bool field initializer") {
test("struct initializers in array") {
val src="""
%option enable_floats
main {
struct Node {
ubyte id
str name
uword array
bool flag
float speed
}
sub start() {
^^Node[] @shared nodes = [
^^Node:[1,"one", 1000, true, 1.111 ],
^^Node:[2,"two", 2000, false, 2.222 ],
^^Node:[3,"three", 3000, true, 3.333 ],
^^Node:[],
^^Node:[],
^^Node:[],
]
}
}"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
}
test("type error for invalid field initializer") {
val src="""
main {
struct Enemy {
@@ -1743,9 +1654,9 @@ main {
sub start() {
^^Enemy @shared e1 = ^^Enemy: []
^^Enemy @shared e2 = ^^Enemy: [1,2,3,true]
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4] ; TODO type error for the boolean
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.555] ; TODO type error for the boolean
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4]
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.444]
e3.elite = 99
e4.elite = 3.444
}
@@ -1753,9 +1664,78 @@ main {
val errors=ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "doesn't match target type"
errors.errors[1] shouldContain "doesn't match target type"
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "value #4 has incompatible type"
errors.errors[1] shouldContain "value #4 has incompatible type"
errors.errors[2] shouldContain "doesn't match target type"
errors.errors[3] shouldContain "doesn't match target type"
}
test("long and short form struct initializers") {
val src="""
%option enable_floats
main {
struct Node {
ubyte id
str name
uword array
bool flag
float perc
}
sub start() {
^^Node[] @shared nodeswithtype = [
^^Node: [1,"one", 1000, true, 1.111],
^^Node: [],
]
^^Node[] @shared nodeswithout = [
[2,"two", 2000, false, 2.222],
[],
]
^^Node @shared nptrwithtype = ^^Node : [1, "one", 1000, false, 3.333]
^^Node @shared nptrwithouttype = [1, "one", 1000, false, 3.333]
}
}"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
}
test("type error for wrong type in pointer array and assignment") {
val src="""
main {
struct Node {
ubyte id
}
struct Foobar {
bool thing
}
sub start() {
^^Node[] onlynodes = [
^^Node: [],
^^Foobar: []
]
uword multipleok = [
^^Node: [],
^^Foobar: []
]
^^Node node = ^^Foobar: []
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 6
errors.errors[0] shouldContain "11:30: initialization value for pointer array"
errors.errors[1] shouldContain "11:30: undefined array type" // a bit redundant but can't be helped
errors.errors[2] shouldContain "13:13: struct initializer element has invalid type"
errors.errors[3] shouldContain "16:28: invalid assignment value"
errors.errors[4] shouldContain "16:28: undefined array type"
errors.errors[5] shouldContain "21:23: cannot assign different pointer type"
}
test("local and global struct pointer qualified name lookups") {
@@ -1982,8 +1962,8 @@ main {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors = errors, writeAssembly = false) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword got ^^ubyte"
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte got ^^uword"
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword or uword but got ^^ubyte"
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte or uword but got ^^uword"
}
test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") {
@@ -2456,4 +2436,73 @@ structdefs {
a5t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
a6t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
}
test("assigning field with same name should not confuse compiler") {
val src="""
main {
sub start() {
ubyte @shared ok = sprites[2].y ; this one is fine...
ubyte @shared y = sprites[2].y ; this used to crash
}
struct Sprite {
uword x
ubyte y
}
^^Sprite[4] @shared sprites
}"""
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
}
test("0-indexed optimizations") {
val src="""
main {
struct Sprite {
uword x
ubyte y
}
^^Sprite[4] @shared sprites
^^Sprite @shared sprptr
sub start() {
sprptr.y = 99
sprptr[0]^^.y = 99
;; sprites[0]^^.y = 99 ; no change here. TODO: this syntax doesn't compile yet...
cx16.r0 = &sprptr[0]
cx16.r2L = sprptr.y
cx16.r0L = sprptr[0].y
cx16.r1L = sprites[0].y ; no change here, need first array element
cx16.r0 = sprites[0] ; no change here, need first array element
cx16.r0 = sprites[0] ; no change here, need first array element
}
}"""
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
val a1 = st[0] as Assignment
val a2 = st[1] as Assignment
val a3 = st[2] as Assignment
val a4 = st[3] as Assignment
val a5 = st[4] as Assignment
val a6 = st[5] as Assignment
val a7 = st[6] as Assignment
a1.target.arrayIndexedDereference shouldBe null
a1.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
a2.target.arrayIndexedDereference shouldBe null
a2.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
(a3.value as? AddressOf)?.identifier?.nameInSource shouldBe listOf("sprptr")
(a4.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
(a5.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
val be6 = a6.value as BinaryExpression // this one is an actual array and we need the first element so no change here
be6.operator shouldBe "."
be6.left shouldBe instanceOf<ArrayIndexedExpression>()
be6.right shouldBe instanceOf<IdentifierReference>()
(a7.value as? ArrayIndexedExpression)?.indexer?.constIndex() shouldBe 0
}
})
+56
View File
@@ -240,6 +240,33 @@ main {
errors.errors[1] shouldContain "9:25: invalid number of arguments"
}
test("invalid number of args check on normal subroutine (in expression)") {
val text="""
main {
sub thing(ubyte a1, ubyte a2) -> ubyte {
return 42
}
sub wrapper(ubyte value) {
value++
}
sub start() {
wrapper(thing(1))
wrapper(thing(1,2))
wrapper(thing(1,2,3))
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "12:33: invalid number of arguments"
errors.errors[1] shouldContain "14:33: invalid number of arguments"
}
test("invalid number of args check on asm subroutine") {
val text="""
main {
@@ -261,6 +288,35 @@ main {
errors.errors[1] shouldContain "9:25: invalid number of arguments"
}
test("invalid number of args check on asm subroutine (in expression)") {
val text="""
main {
asmsub thing(ubyte a1 @A, ubyte a2 @Y) -> ubyte @A {
%asm {{
rts
}}
}
sub wrapper(ubyte value) {
value++
}
sub start() {
wrapper(thing(1))
wrapper(thing(1,2))
wrapper(thing(1,2,3))
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "14:33: invalid number of arguments"
errors.errors[1] shouldContain "16:33: invalid number of arguments"
}
test("invalid number of args check on call to label and builtin func") {
val text="""
main {
+2 -2
View File
@@ -88,8 +88,8 @@ class TestSymbolTable: FunSpec({
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false,node)
stVar1.setOnetimeInitNumeric(99.0)
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false, node)
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null), StArrayElement(2.2, null, null), StArrayElement(3.3, null, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null), StArrayElement(0.0, null, null), StArrayElement(0.0, null, null))
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null, null, null), StArrayElement(2.2, null, null, null, null), StArrayElement(3.3, null, null,null, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null, null, null), StArrayElement(0.0, null, null,null, null), StArrayElement(0.0, null, null,null, null))
val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3u, ZeropageWish.DONTCARE, 0u, false, node)
+31 -30
View File
@@ -49,6 +49,7 @@ class TestAbstractZeropage: FunSpec({
noSysInit = false,
romable = false,
compTarget = C64Target(),
compilerVersion="99.99",
loadAddress = 999u,
memtopAddress = 0xffffu
)
@@ -69,7 +70,7 @@ class TestC64Zeropage: FunSpec({
floats = false,
noSysInit = false,
romable = false,
compTarget = c64target, loadAddress = 999u, memtopAddress = 0xffffu
compTarget = c64target, compilerVersion="99.99", loadAddress = 999u, memtopAddress = 0xffffu
))
var result = zp.allocate("", DataType.UBYTE, null, null, errors)
@@ -84,33 +85,33 @@ class TestC64Zeropage: FunSpec({
}
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
zp3.allocate("", DataType.FLOAT, null, null, errors)
}
test("testZpModesWithFloats") {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
}
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
}
}
test("testZpDontuse") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
println(zp.free)
zp.availableBytes() shouldBe 0
val result = zp.allocate("", DataType.BYTE, null, null, errors)
@@ -118,13 +119,13 @@ class TestC64Zeropage: FunSpec({
}
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
zp1.availableBytes() shouldBe 15
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp2.availableBytes() shouldBe 85
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp3.availableBytes() shouldBe 94
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp4.availableBytes() shouldBe 205
zp4.allocate("test", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 204
@@ -133,7 +134,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp1.availableBytes() shouldBe 205
4u shouldNotBeIn zp1.free
5u shouldNotBeIn zp1.free
@@ -146,7 +147,7 @@ class TestC64Zeropage: FunSpec({
200u shouldBeIn zp1.free
255u shouldBeIn zp1.free
199u shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp2.availableBytes() shouldBe 105
4u shouldNotBeIn zp2.free
5u shouldNotBeIn zp2.free
@@ -159,7 +160,7 @@ class TestC64Zeropage: FunSpec({
200u shouldNotBeIn zp2.free
255u shouldNotBeIn zp2.free
199u shouldBeIn zp2.free
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp2.availableBytes() shouldBe 105
4u shouldBeIn zp3.free
5u shouldBeIn zp3.free
@@ -168,7 +169,7 @@ class TestC64Zeropage: FunSpec({
}
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
zp.availableBytes() shouldBe 15
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -190,7 +191,7 @@ class TestC64Zeropage: FunSpec({
}
test("testFullAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
zp.availableBytes() shouldBe 205
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -222,7 +223,7 @@ class TestC64Zeropage: FunSpec({
}
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, "99.99", 999u, 0xffffu))
zp.availableBytes() shouldBe 15
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
@@ -239,7 +240,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedLocations") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, "99.99", 999u, 0xffffu))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
@@ -252,18 +253,18 @@ class TestCx16Zeropage: FunSpec({
val cx16target = Cx16Target()
test("testReservedLocations") {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
test("testFreeSpacesBytes") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, "99.99", 999u, 0xffffu))
zp1.availableBytes() shouldBe 86
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
zp2.availableBytes() shouldBe 173
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
zp3.availableBytes() shouldBe 214
zp3.allocate("test", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 213
@@ -272,7 +273,7 @@ class TestCx16Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
zp1.availableBytes() shouldBe 214
0x22u shouldNotBeIn zp1.free
0x23u shouldNotBeIn zp1.free
@@ -284,7 +285,7 @@ class TestCx16Zeropage: FunSpec({
}
test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, "99.99", 999u, 0xffffu))
zp1.allocatedVariables["test"] shouldBe null
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
+5 -3
View File
@@ -5,7 +5,7 @@ import io.kotest.matchers.string.shouldContain
import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module
import prog8.ast.Program
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.source.SourceCode
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
@@ -38,10 +38,12 @@ class TestAstToSourceText: AnnotationSpec() {
}
@Test
fun testMentionsInternedStringsModule() {
fun testMentionsProg8ContainerModules() {
val orig = SourceCode.Text("\n")
val (txt, _) = roundTrip(parseModule(orig))
txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME")
PROG8_CONTAINER_MODULES.forEach {
txt shouldContain Regex(";.*$it")
}
}
@Test
+11
View File
@@ -466,4 +466,15 @@ main {
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
}
test("const long with small values") {
val src="""
main {
sub start() {
const long notkaputt = 42
cx16.r0L = notkaputt
}
}"""
compileText(Cx16Target(), true, src, outputDir, writeAssembly = false) shouldNotBe null
}
})
+6 -5
View File
@@ -13,10 +13,11 @@ import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.statements.Block
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.ast.PtBlock
import prog8.code.core.Position
import prog8.code.source.SourceCode
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
@@ -30,7 +31,7 @@ class TestProgram: FunSpec({
context("Constructor") {
test("withNameBuiltinsAndMemsizer") {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.modules.size shouldBe 1
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
program.modules[0].program shouldBeSameInstanceAs program
program.modules[0].parent shouldBeSameInstanceAs program.namespace
@@ -45,7 +46,7 @@ class TestProgram: FunSpec({
val retVal = program.addModule(m1)
retVal shouldBeSameInstanceAs program
program.modules.size shouldBe 2
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size + 1
m1 shouldBeIn program.modules
m1.program shouldBeSameInstanceAs program
m1.parent shouldBeSameInstanceAs program.namespace
@@ -163,7 +164,7 @@ datablock2 ${'$'}8000 {
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!!
result.compilerAst.allBlocks.size shouldBeGreaterThan 5
result.compilerAst.modules.drop(2).all { it.isLibrary } shouldBe true
result.compilerAst.modules.drop(PROG8_CONTAINER_MODULES.size+1).all { it.isLibrary } shouldBe true
val mainMod = result.compilerAst.modules[0]
mainMod.name shouldStartWith "on_the_fly"
result.compilerAst.modules[1].name shouldBe "prog8_interned_strings"
@@ -183,7 +184,7 @@ datablock2 ${'$'}8000 {
blocks[1].name shouldBe "p8_sys_startup"
blocks[2].name shouldBe "p8b_otherblock1"
blocks[3].name shouldBe "p8b_otherblock2"
blocks[4].name shouldBe "prog8_interned_strings"
blocks[4].name shouldBe INTERNED_STRINGS_MODULENAME
blocks[5].name shouldBe "txt"
blocks[5].library shouldBe true
blocks[13].name shouldBe "p8b_datablock2"
+42 -3
View File
@@ -871,6 +871,7 @@ main {
DataType.forDt(BaseDataType.FLOAT).isFloat shouldBe true
DataType.arrayFor(BaseDataType.UBYTE, true).isUnsignedByteArray shouldBe true
DataType.arrayFor(BaseDataType.LONG).isLongArray shouldBe true
DataType.arrayFor(BaseDataType.FLOAT).isFloatArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isUnsignedWordArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isArray shouldBe true
@@ -886,9 +887,6 @@ main {
shouldThrow<NoSuchElementException> {
DataType.arrayFor(BaseDataType.ARRAY)
}
shouldThrow<NoSuchElementException> {
DataType.arrayFor(BaseDataType.LONG)
}
shouldThrow<NoSuchElementException> {
DataType.arrayFor(BaseDataType.UNDEFINED)
}
@@ -1157,5 +1155,46 @@ main {
decls.all { it.datatype.isPointerArray } shouldBe true
}
test("on..call in nested scope compiles correctly with temp variable introduced") {
val src="""
main {
sub start() {
if_cs {
on cx16.r0L call (func,func)
}
}
sub func() {
}
}"""
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
}
test("breakpoint after expression") {
val src="""
main {
ubyte a,b
sub start() {
func1()
func2()
}
sub func1() {
a = b
%breakpoint ; parse error
}
sub func2() {
a = b
%breakpoint! ; parse ok
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 3
errors.errors[1] shouldContain "12:10: undefined symbol: breakpoint"
}
})
@@ -88,7 +88,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), 999u, 0xffffu)
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), "99.99", 999u, 0xffffu)
val astchecker = AstChecker(program, errors, options)
astchecker.visit(program)
errors.report()
+2
View File
@@ -28,12 +28,14 @@ internal object DummyMemsizer : IMemSizer {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.LONG -> numElements*4
BaseDataType.FLOAT -> numElements*5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
+41 -4
View File
@@ -3,6 +3,8 @@ package prog8tests.vm
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.ints.shouldBeLessThanOrEqual
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@@ -12,10 +14,7 @@ import prog8.ast.statements.Assignment
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.intermediate.IRDataType
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.intermediate.*
import prog8.vm.VmRunner
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -548,4 +547,42 @@ main {
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), true)
}
test("correct reg types with sqrt") {
val src= """
main {
sub start() {
cx16.r0L= sqrt(cx16.r1)
}
}"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
}
test("SSA basic blocks") {
val src= """
main {
sub start() {
func()
}
sub func() {
if cx16.r0<10 or cx16.r0>319 {
cx16.r1++
}
}
}"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
val irProgram = IRFileReader().read(virtfile)
val func = irProgram.blocks[0].children[1] as IRSubroutine
func.label shouldBe "main.func"
func.chunks.size shouldBe 11
for(chunk in func.chunks) {
if(chunk.next == null) {
chunk.instructions.last().opcode shouldBeIn OpcodesThatBranchUnconditionally
}
val branches = chunk.instructions.filter { it.opcode in OpcodesThatEndSSAblock }
branches.size shouldBeLessThanOrEqual 1
}
}
})
@@ -213,7 +213,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
output(") ")
if(subroutine.asmClobbers.isNotEmpty()) {
output("-> clobbers (")
output(" clobbers (")
val regs = subroutine.asmClobbers.toList().sorted()
for(r in regs) {
output(r.toString())
@@ -545,9 +545,9 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(onGoto: OnGoto) {
output(if(onGoto.isCall) "selectcall " else "selectgoto ")
output("on ")
onGoto.index.accept(this)
output(" from (")
output(if(onGoto.isCall) " call (" else " goto (")
onGoto.labels.forEachIndexed { idx, label ->
label.accept(this)
if(idx!=onGoto.labels.lastIndex)
@@ -571,7 +571,18 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(deref: ArrayIndexedPtrDereference) {
output("???? Array Indexed Ptr Dereference should have been converted to other AST nodes ????")
for(c in deref.chain) {
output(c.first)
c.second?.let {
output("[")
it.accept(this)
output("]")
}
if(c !== deref.chain.last())
output("^^.")
}
if(deref.derefLast)
output("^^")
}
override fun visit(initializer: StaticStructInitializer) {
+1
View File
@@ -427,6 +427,7 @@ fun defaultZero(dt: BaseDataType, position: Position) = when(dt) {
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, 0.0, position)
BaseDataType.UWORD, BaseDataType.STR -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, 0.0, position)
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, 0.0, position)
BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, 0.0, position)
BaseDataType.POINTER -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
-4
View File
@@ -1,6 +1,5 @@
package prog8.ast
import prog8.ast.expressions.IdentifierReference
import prog8.code.core.Position
/**
@@ -19,6 +18,3 @@ open class SyntaxError(override var message: String, val position: Position) : A
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "${position.toClickableStr()} Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
+14 -11
View File
@@ -6,6 +6,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.*
import prog8.code.source.SourceCode
@@ -22,17 +23,19 @@ class Program(val name: String,
val namespace: GlobalNamespace = GlobalNamespace(_modules)
init {
// insert a container module for all interned strings later
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(INTERNED_STRINGS_MODULENAME))
val block = Block(INTERNED_STRINGS_MODULENAME, null, mutableListOf(), true, Position.DUMMY)
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
block.statements.add(directive)
directive.linkParents(block)
internedStringsModule.statements.add(block)
// insert container modules for all interned strings and struct instances
PROG8_CONTAINER_MODULES.forEach { containername ->
val module = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(containername))
val block = Block(containername, null, mutableListOf(), true, Position.DUMMY)
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
block.statements.add(directive)
directive.linkParents(block)
module.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
_modules.add(0, module)
module.linkParents(namespace)
module.program = this
}
}
fun addModule(module: Module): Program {
@@ -67,7 +70,7 @@ class Program(val name: String,
}
val toplevelModule: Module
get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME }
get() = modules.first { it.name !in PROG8_CONTAINER_MODULES }
private val internedStringsReferenceCounts = mutableMapOf<VarDecl, Int>()
+1 -1
View File
@@ -150,7 +150,7 @@ private class SymbolDumper(val skipLibraries: Boolean): IAstVisitor {
}
output(") ")
if(subroutine.asmClobbers.isNotEmpty()) {
output("-> clobbers (")
output(" clobbers (")
val regs = subroutine.asmClobbers.toList().sorted()
for(r in regs) {
output(r.toString())
@@ -372,7 +372,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
else if(literalText.length>2)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(16)
integer = literalText.lowercase().toLong(16).toInt()
} catch(x: NumberFormatException) {
throw SyntaxError("invalid hexadecimal literal ${x.message}", ctx.toPosition())
}
@@ -126,11 +126,13 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
"~" -> {
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.UBYTE)
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.UWORD)
else if(inferred.isLong) InferredTypes.knownFor(BaseDataType.LONG) // TODO should be ULONG, but we don't have that type yet
else InferredTypes.unknown()
}
"-" -> {
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.BYTE)
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.WORD)
else if(inferred.isLong) InferredTypes.knownFor(BaseDataType.LONG)
else inferred
}
else -> throw FatalAstException("weird prefix expression operator")
@@ -179,7 +181,12 @@ class BinaryExpression(
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
override fun referencesIdentifier(nameInSource: List<String>): Boolean {
return if(operator==".")
left.referencesIdentifier(nameInSource)
else
left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
}
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
@@ -326,6 +333,7 @@ class BinaryExpression(
BaseDataType.BYTE -> Pair(DataType.BYTE, left)
BaseDataType.UWORD -> Pair(DataType.UWORD, left)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.LONG -> Pair(DataType.LONG, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
@@ -336,6 +344,7 @@ class BinaryExpression(
BaseDataType.BYTE -> Pair(DataType.BYTE, null)
BaseDataType.UWORD -> Pair(DataType.WORD, left)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.LONG -> Pair(DataType.LONG, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
@@ -346,6 +355,7 @@ class BinaryExpression(
BaseDataType.BYTE -> Pair(DataType.WORD, right)
BaseDataType.UWORD -> Pair(DataType.UWORD, null)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.LONG -> Pair(DataType.LONG, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
@@ -356,6 +366,18 @@ class BinaryExpression(
BaseDataType.BYTE -> Pair(DataType.WORD, right)
BaseDataType.UWORD -> Pair(DataType.WORD, right)
BaseDataType.WORD -> Pair(DataType.WORD, null)
BaseDataType.LONG -> Pair(DataType.LONG, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
BaseDataType.LONG -> {
when (rightDt.base) {
BaseDataType.UBYTE -> Pair(DataType.LONG, right)
BaseDataType.BYTE -> Pair(DataType.LONG, right)
BaseDataType.UWORD -> Pair(DataType.LONG, right)
BaseDataType.WORD -> Pair(DataType.LONG, right)
BaseDataType.LONG -> Pair(DataType.LONG, null)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
@@ -1123,7 +1145,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
}
// otherwise, select the "biggest" datatype based on the elements in the array.
require(value.isNotEmpty()) { "can't determine type of empty array" }
if(value.isEmpty()) return InferredTypes.unknown()
val datatypesInArray = value.map { it.inferType(program) }
if(datatypesInArray.any{ it.isUnknown })
return InferredTypes.unknown()
@@ -1318,9 +1340,30 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
else
definingScope.lookup(nameInSource)
fun targetVarDecl(): VarDecl? = targetStatement() as? VarDecl
fun targetSubroutine(): Subroutine? = targetStatement() as? Subroutine
fun targetStructDecl(): StructDecl? = targetStatement() as? StructDecl
fun targetVarDecl(): VarDecl? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetVarDecl()
else
t as? VarDecl
}
fun targetSubroutine(): Subroutine? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetSubroutine()
else
t as? Subroutine
}
fun targetStructDecl(): StructDecl? {
// follows aliases
val t = targetStatement()
return if(t is Alias)
t.target.targetStructDecl()
else
t as? StructDecl
}
fun targetStructFieldRef(): StructFieldRef? {
if(nameInSource.size<2) return null
return targetStatement() as? StructFieldRef
@@ -1472,7 +1515,7 @@ class FunctionCallExpression(override var target: IdentifierReference,
override fun copy() = FunctionCallExpression(target.copy(), args.map { it.copy() }.toMutableList(), position)
override val isSimple = when (target.nameInSource.singleOrNull()) {
in arrayOf("msb", "lsb", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple }
in arrayOf("msb", "lsb", "mkword", "mklong", "mklong2", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple }
else -> false
}
override fun replaceChildNode(node: Node, replacement: Node) {
@@ -1783,6 +1826,35 @@ class ArrayIndexedPtrDereference(
}
}
if(parent !is BinaryExpression || (parent as BinaryExpression).operator!=".") {
val indexPosition = this.chain.indexOfFirst { it.second!=null }
val identifier = this.chain.take(indexPosition+1).map {it.first}
val target = this.definingScope.lookup(identifier)
if(target is VarDecl && (target.datatype.isPointer || target.datatype.isPointerArray)) {
val sub = target.datatype.subType
if(sub!=null) {
val field = this.chain.drop(indexPosition+1).singleOrNull()
if(field==null) {
return InferredTypes.knownFor(DataType.structInstance(sub))
} else {
val fieldDt= sub.getFieldType(field.first)!!
return if(derefLast)
InferredTypes.knownFor(fieldDt.dereference())
else
InferredTypes.knownFor(fieldDt)
}
} else {
require(indexPosition==this.chain.size-1) {"when indexing primitive pointer type there cannot be a field after it"}
return if(derefLast)
InferredTypes.knownFor(target.datatype.dereference())
else
InferredTypes.knownFor(target.datatype)
}
}
}
// too hard to determine the type....?
return InferredTypes.unknown()
}
@@ -1820,7 +1892,13 @@ class StaticStructInitializer(var structname: IdentifierReference,
override fun referencesIdentifier(nameInSource: List<String>): Boolean = structname.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UWORD)
override fun inferType(program: Program): InferredTypes.InferredType {
val struct = structname.targetStructDecl()
return if(struct==null)
InferredTypes.unknown()
else
InferredTypes.knownFor(DataType.pointer(struct))
}
}
@@ -56,6 +56,7 @@ object InferredTypes {
val isBool = datatype?.isBool==true
val isBytes = datatype?.isByte==true
val isWords = datatype?.isWord==true
val isLong = datatype?.isLong==true
val isPointer = datatype?.isPointer==true
val isStructInstance = datatype?.isStructInstance==true
val isUnsignedWord = datatype?.isUnsignedWord==true
@@ -333,7 +333,7 @@ class VarDecl(
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && (value==null || node===value))
value = replacement // note: any datatype differences between the value and the decl itself, will be fixed by a separate ast walker step
replacement.parent = this
replacement.linkParents(this)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@@ -414,6 +414,15 @@ class StructDecl(override val name: String, val fields: Array<Pair<DataType, Str
override fun getFieldType(name: String): DataType? = fields.firstOrNull { it.second==name }?.first
override val scopedNameString by lazy { scopedName.joinToString(".") }
fun offsetof(fieldname: String, sizer: IMemSizer): UByte? {
fields.fold(0) { offset, field ->
if (field.second == fieldname)
return offset.toUByte()
offset + sizer.memorySize(field.first, 1)
}
return null
}
}
class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, val type: DataType, override val name: String, override val position: Position): Statement(), INamedStatement {
+1
View File
@@ -1,5 +1,6 @@
; all library modules for the c128 compiler target
%import bcd
%import buffers
%import compression
%import conv
+1
View File
@@ -1,5 +1,6 @@
; all library modules for the c64 compiler target
%import bcd
%import buffers
%import compression
%import conv
+2 -1
View File
@@ -1,7 +1,8 @@
; all library modules for the cx16 compiler target
%import buffers
%import bcd
%import bmx
%import buffers
%import compression
%import conv
%import coroutines
+1
View File
@@ -1,5 +1,6 @@
; all library modules for the pet32 compiler target
%import bcd
%import buffers
%import compression
%import conv
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more