mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
775
Commits
v12.2-beta
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63e2a45f09 | ||
|
|
6076b01f6d | ||
|
|
d0bf2c3dcb | ||
|
|
d9bf6f090e | ||
|
|
e08a995067 | ||
|
|
da2288db93 | ||
|
|
31b5cddb96 | ||
|
|
264b894909 | ||
|
|
09ae0c6322 | ||
|
|
9ca5db3436 | ||
|
|
375214b5e3 | ||
|
|
1da37e4119 | ||
|
|
16f7d588a7 | ||
|
|
f6c8a3f2ed | ||
|
|
5619898113 | ||
|
|
ae0414631e | ||
|
|
e3ca9ae4a8 | ||
|
|
38c60a2d75 | ||
|
|
5e83c44300 | ||
|
|
99ff5768c5 | ||
|
|
fba4bb5272 | ||
|
|
17322a08ec | ||
|
|
06d2f9ebbe | ||
|
|
3a5f2c33ab | ||
|
|
d523ecd1e1 | ||
|
|
159634a99f | ||
|
|
55dcb45230 | ||
|
|
de75342198 | ||
|
|
30d3b76787 | ||
|
|
8bbe5bd209 | ||
|
|
da7ea294ef | ||
|
|
05d8acc4bb | ||
|
|
6cac707fd1 | ||
|
|
dea43c99bb | ||
|
|
0b902f06b5 | ||
|
|
733a3fb752 | ||
|
|
460adb9e02 | ||
|
|
3c22dfc11a | ||
|
|
dfb3257c04 | ||
|
|
8bcd176746 | ||
|
|
fd3add76e4 | ||
|
|
4d0a7ff8bf | ||
|
|
e1f2faabe7 | ||
|
|
25bd60368a | ||
|
|
f61c1dc7ef | ||
|
|
eda21a6ebf | ||
|
|
c0267e37b0 | ||
|
|
78f9df203c | ||
|
|
d3e10b4974 | ||
|
|
c4679bd8af | ||
|
|
e7dfe79919 | ||
|
|
31dbbc3fe1 | ||
|
|
55b7239553 | ||
|
|
5702e5aa2a | ||
|
|
592637856f | ||
|
|
233ca21492 | ||
|
|
1a84616363 | ||
|
|
d32b70bb75 | ||
|
|
73241b0261 | ||
|
|
e515dee561 | ||
|
|
3d78415088 | ||
|
|
0189903a63 | ||
|
|
9c9552174e | ||
|
|
be61d37fd1 | ||
|
|
70bb199e12 | ||
|
|
09737187ad | ||
|
|
de39533fba | ||
|
|
8bc2b177a0 | ||
|
|
09eba995ae | ||
|
|
b739d7b663 | ||
|
|
5e709fca7e | ||
|
|
cc8df33418 | ||
|
|
125af552ff | ||
|
|
29254e892b | ||
|
|
a1a48405b0 | ||
|
|
acc8705ea8 | ||
|
|
bbff472457 | ||
|
|
353b0c84b7 | ||
|
|
329b3df4a3 | ||
|
|
fe90ca572d | ||
|
|
26bbd6c1fe | ||
|
|
822cfd4f35 | ||
|
|
0807d07ee5 | ||
|
|
2e3aa76a2f | ||
|
|
3beb565c84 | ||
|
|
b1642f4f7a | ||
|
|
1f8b42fae5 | ||
|
|
13828ea932 | ||
|
|
e550143ed0 | ||
|
|
47e6066a36 | ||
|
|
266f4ab380 | ||
|
|
e5954542dd | ||
|
|
8c2bc15b41 | ||
|
|
c9a7dc05d9 | ||
|
|
9d023c9568 | ||
|
|
a1ce45e409 | ||
|
|
09d469714b | ||
|
|
38db571f20 | ||
|
|
5f3b587af1 | ||
|
|
e74c34a5ab | ||
|
|
2b156ec652 | ||
|
|
e38f71fd2d | ||
|
|
6c107379df | ||
|
|
99f29cb9b9 | ||
|
|
d6ea0782e2 | ||
|
|
bdee93dc8f | ||
|
|
3d675a460e | ||
|
|
bd1747926c | ||
|
|
cff7732189 | ||
|
|
fa966086e9 | ||
|
|
ec4b47ac0d | ||
|
|
5222562493 | ||
|
|
c8cda7947a | ||
|
|
ffe855f295 | ||
|
|
8a66dabf6d | ||
|
|
b8c975704a | ||
|
|
2ef0ddd42c | ||
|
|
9a83b7455a | ||
|
|
9fce07565c | ||
|
|
d703d678b4 | ||
|
|
90a7c41586 | ||
|
|
b1a9ba929d | ||
|
|
75ab3ccfbd | ||
|
|
c031737da0 | ||
|
|
2f905ef6b6 | ||
|
|
4cbf93da85 | ||
|
|
976b64185d | ||
|
|
2050fcb4ee | ||
|
|
69525ae67f | ||
|
|
4eaba62f1e | ||
|
|
bf696bcf5e | ||
|
|
34bbb46514 | ||
|
|
f086918e6f | ||
|
|
521f7ffbe6 | ||
|
|
2d070aaec4 | ||
|
|
044a4a1789 | ||
|
|
6de6a81d4b | ||
|
|
815f4109a5 | ||
|
|
a49f080ca7 | ||
|
|
ac4ae0440f | ||
|
|
773f1f2292 | ||
|
|
efee8bde68 | ||
|
|
ddfdd9097d | ||
|
|
ef64e2f9a4 | ||
|
|
2f6ab002d8 | ||
|
|
ba76fbfe85 | ||
|
|
7f3b2e8aa6 | ||
|
|
f254fc2b62 | ||
|
|
457d55b392 | ||
|
|
b21a984dbb | ||
|
|
7427e6ddad | ||
|
|
192e0086f9 | ||
|
|
c8972c9561 | ||
|
|
7ba2ee9857 | ||
|
|
a025d68291 | ||
|
|
81794aded3 | ||
|
|
9621552056 | ||
|
|
5685a1f229 | ||
|
|
b014c78c94 | ||
|
|
ca729d279e | ||
|
|
3b0faa749a | ||
|
|
22a2f8cbc5 | ||
|
|
468850bc47 | ||
|
|
20374f4059 | ||
|
|
ceffa4d83d | ||
|
|
af14562e2b | ||
|
|
dbba6a94fa | ||
|
|
302e4bdf8e | ||
|
|
ccc26a0eb2 | ||
|
|
e017c541bb | ||
|
|
3d5a7795d0 | ||
|
|
af06fdfffc | ||
|
|
e811ecdbde | ||
|
|
4c65049817 | ||
|
|
a7cbc2c5bf | ||
|
|
d51ca62431 | ||
|
|
9ac60eec40 | ||
|
|
c55b16f4cb | ||
|
|
bd0457f262 | ||
|
|
ce2b298d6f | ||
|
|
cf7afaf77a | ||
|
|
54507ad9b3 | ||
|
|
30435b6778 | ||
|
|
619a215a87 | ||
|
|
c12e27dacc | ||
|
|
db32b81d7a | ||
|
|
687d86d36e | ||
|
|
e074fe614f | ||
|
|
bbfb09e467 | ||
|
|
1358a535e3 | ||
|
|
bc62b2de81 | ||
|
|
58f544fe14 | ||
|
|
7dc05688bf | ||
|
|
aac7d409dc | ||
|
|
0771105694 | ||
|
|
9a461628f2 | ||
|
|
c8f309307f | ||
|
|
b702ea50da | ||
|
|
b0f1f655c3 | ||
|
|
59c41ad129 | ||
|
|
60eeaba791 | ||
|
|
30266c1b92 | ||
|
|
05ba1237dc | ||
|
|
9d537ab3c0 | ||
|
|
1ba08ec6ec | ||
|
|
199c3e4efb | ||
|
|
a7c035f4d1 | ||
|
|
05cbe74a60 | ||
|
|
d652d7a924 | ||
|
|
739fb6dc42 | ||
|
|
9c53791ef4 | ||
|
|
420a9b7249 | ||
|
|
9bf8318b1c | ||
|
|
94822b8662 | ||
|
|
b0d50c2a68 | ||
|
|
deae2e6422 | ||
|
|
c8d29e6dc9 | ||
|
|
a7b14c2931 | ||
|
|
a14a8f4e62 | ||
|
|
cf50372515 | ||
|
|
da0df8f133 | ||
|
|
27070f31e8 | ||
|
|
aa3f8e3038 | ||
|
|
277e9c09bb | ||
|
|
70521ce301 | ||
|
|
93b5faa038 | ||
|
|
f33814a594 | ||
|
|
986ae35821 | ||
|
|
937b520838 | ||
|
|
250ddda11b | ||
|
|
bde52cc134 | ||
|
|
8e739febf0 | ||
|
|
3fcb4b4262 | ||
|
|
56643f43df | ||
|
|
a9cad9b51d | ||
|
|
8647b136bd | ||
|
|
4179f2c571 | ||
|
|
02a118ec0e | ||
|
|
561f31329d | ||
|
|
2bbbacc46e | ||
|
|
c3920de251 | ||
|
|
14bf07907b | ||
|
|
c657935f10 | ||
|
|
c08def79ae | ||
|
|
296295011e | ||
|
|
de20c09790 | ||
|
|
4199ab4200 | ||
|
|
fe6850816c | ||
|
|
eff1a9fc53 | ||
|
|
5857382242 | ||
|
|
dfda43e747 | ||
|
|
1029929111 | ||
|
|
ae55109a18 | ||
|
|
2ce7a63c1e | ||
|
|
da6934ea33 | ||
|
|
2c8ccb706e | ||
|
|
0d0d707976 | ||
|
|
4912c96f78 | ||
|
|
79dd8d6b92 | ||
|
|
f750caa833 | ||
|
|
d17c978892 | ||
|
|
45cb50ebb1 | ||
|
|
2cd2bb0121 | ||
|
|
5b890ac5d5 | ||
|
|
894a3e90db | ||
|
|
234468690e | ||
|
|
5f50b254a2 | ||
|
|
c5b17ae880 | ||
|
|
4b320013cf | ||
|
|
f3de26bf6c | ||
|
|
65a1300147 | ||
|
|
ba6179a4d3 | ||
|
|
6b91a464c0 | ||
|
|
08ac5eff3e | ||
|
|
7de39bbd62 | ||
|
|
198cf4d722 | ||
|
|
b0b8513cb8 | ||
|
|
82cc66c0cc | ||
|
|
c81d9b25ac | ||
|
|
3aab457a47 | ||
|
|
c89f870dd0 | ||
|
|
ffb0a4d603 | ||
|
|
df659d285b | ||
|
|
2c2664c91f | ||
|
|
e0147a4a7e | ||
|
|
859223c98b | ||
|
|
7bdb9bc853 | ||
|
|
4e87aa6b85 | ||
|
|
02667e9916 | ||
|
|
9d57cbe263 | ||
|
|
8b25ff2390 | ||
|
|
2cb753caf8 | ||
|
|
34f0bea4e8 | ||
|
|
82828606e6 | ||
|
|
ae751c4af4 | ||
|
|
51b98204e9 | ||
|
|
12f375e070 | ||
|
|
54a1a0704a | ||
|
|
08b4168311 | ||
|
|
4fdd12929a | ||
|
|
cc4f27d121 | ||
|
|
61041f4502 | ||
|
|
673ed91ee3 | ||
|
|
65b6280618 | ||
|
|
e50e175522 | ||
|
|
fa1ceab78f | ||
|
|
7eab0278b3 | ||
|
|
36b12e9897 | ||
|
|
69eaaf29c4 | ||
|
|
69def73f3f | ||
|
|
9c2d8541c3 | ||
|
|
6c93254758 | ||
|
|
a68cdd13a6 | ||
|
|
295dfada61 | ||
|
|
d978be2f9d | ||
|
|
0a4601721a | ||
|
|
d870c39aa4 | ||
|
|
2b93efbb33 | ||
|
|
c563562a6f | ||
|
|
92a0d3cb1b | ||
|
|
21d45a0a90 | ||
|
|
c87afc03ef | ||
|
|
8dc9c4d3c2 | ||
|
|
e264aff607 | ||
|
|
9591aad814 | ||
|
|
3208ef1f1a | ||
|
|
36fcfa6965 | ||
|
|
ba7bf3f4fe | ||
|
|
fcc93b385d | ||
|
|
4c6d9b8f5f | ||
|
|
6a3ee52bd5 | ||
|
|
8dec449a1c | ||
|
|
a09942254d | ||
|
|
d702ace09f | ||
|
|
f011d3f100 | ||
|
|
e404530ba4 | ||
|
|
48da22866c | ||
|
|
2e529f8849 | ||
|
|
79a5ddcdd8 | ||
|
|
7aecb8d6eb | ||
|
|
73768e1876 | ||
|
|
3514bd61ab | ||
|
|
a7f60a571d | ||
|
|
3705064157 | ||
|
|
208205faea | ||
|
|
5af6afef61 | ||
|
|
a5a535806b | ||
|
|
d582c1edf6 | ||
|
|
7275137782 | ||
|
|
f11da0488a | ||
|
|
ed1abe7e6b | ||
|
|
4dedc27e5b | ||
|
|
e70a1ed403 | ||
|
|
c53e72c5b2 | ||
|
|
5ba494c5e5 | ||
|
|
b353904d30 | ||
|
|
a58a643d0d | ||
|
|
823ecfac6f | ||
|
|
22035077af | ||
|
|
da8c3eda92 | ||
|
|
41c2b9c039 | ||
|
|
5c16ebe377 | ||
|
|
c7e25e323a | ||
|
|
1e2fc4d6f9 | ||
|
|
b5dfc0cd97 | ||
|
|
799486eb1b | ||
|
|
ed67a259de | ||
|
|
d276fb9ed5 | ||
|
|
e0329e4457 | ||
|
|
a3161fa117 | ||
|
|
9c8e81b566 | ||
|
|
e2e72a1791 | ||
|
|
ec9567a669 | ||
|
|
b3b983c7ec | ||
|
|
14ae579f00 | ||
|
|
45f48d2e88 | ||
|
|
8d6ed7712d | ||
|
|
eea185bb83 | ||
|
|
99d28ae557 | ||
|
|
528762d961 | ||
|
|
c6e9a45293 | ||
|
|
6880bd2a05 | ||
|
|
7547fb7747 | ||
|
|
ff0e922674 | ||
|
|
71f66171c4 | ||
|
|
c1f76cf073 | ||
|
|
c2d151920d | ||
|
|
c916570abd | ||
|
|
bdab975d14 | ||
|
|
1693e4ff9c | ||
|
|
70d81298c1 | ||
|
|
6bf304b67b | ||
|
|
a5978a913b | ||
|
|
e528f0ccaa | ||
|
|
e5f488dcfb | ||
|
|
1de1c10b41 | ||
|
|
c47a6bde0e | ||
|
|
b8d38eccca | ||
|
|
4c2144eb0d | ||
|
|
68399e8370 | ||
|
|
7b61b066e6 | ||
|
|
bd2a47fb54 | ||
|
|
2521df21e7 | ||
|
|
ddb6b597d6 | ||
|
|
c24fc9e81d | ||
|
|
68f0d2285f | ||
|
|
25f883bb85 | ||
|
|
adc6d6626a | ||
|
|
90ff904b6e | ||
|
|
ba54601eab | ||
|
|
9937990618 | ||
|
|
1c71f702f4 | ||
|
|
3d3c567ea6 | ||
|
|
539c75bbf6 | ||
|
|
2683b6388a | ||
|
|
e007d127f0 | ||
|
|
e5d9609db1 | ||
|
|
051f3809cd | ||
|
|
b5fd07bb91 | ||
|
|
4097d51514 | ||
|
|
577815a1f4 | ||
|
|
c2a942dd45 | ||
|
|
82342268da | ||
|
|
30e576e253 | ||
|
|
a5782c300a | ||
|
|
84f720b63b | ||
|
|
b8cded4373 | ||
|
|
368dce93d9 | ||
|
|
644226f925 | ||
|
|
42e8e91034 | ||
|
|
af80ec6502 | ||
|
|
cf714d8e0c | ||
|
|
86c2f711ed | ||
|
|
86c01e9d7b | ||
|
|
7138ee024b | ||
|
|
b9f65186cb | ||
|
|
27aeafef0d | ||
|
|
16901212d4 | ||
|
|
49f5c30e8c | ||
|
|
bef1678c2a | ||
|
|
3459ffcdb9 | ||
|
|
4cfe9b2729 | ||
|
|
a919ea63e4 | ||
|
|
97e5a6f7bb | ||
|
|
79c05ae396 | ||
|
|
f25ea95bdf | ||
|
|
45bf8ba72a | ||
|
|
07910fc543 | ||
|
|
c64fdcffa8 | ||
|
|
c0b8cf827d | ||
|
|
fc38847af4 | ||
|
|
fea52c6145 | ||
|
|
4b20440efe | ||
|
|
c39410afc2 | ||
|
|
93b953cf60 | ||
|
|
01c82f92eb | ||
|
|
13e032e1a3 | ||
|
|
8405e6f2a3 | ||
|
|
491b28f6b6 | ||
|
|
9286d6b2c8 | ||
|
|
d144235dc9 | ||
|
|
c3b5ee3dfd | ||
|
|
b0e0058310 | ||
|
|
9cbbf85a85 | ||
|
|
2fa82ff3ad | ||
|
|
9936a855ab | ||
|
|
3e03980478 | ||
|
|
e042756453 | ||
|
|
84d5247b78 | ||
|
|
4682462c19 | ||
|
|
b6e503a67b | ||
|
|
dedae29d72 | ||
|
|
bb1d1e2007 | ||
|
|
0aa6c130e8 | ||
|
|
092116de1d | ||
|
|
bf0733f8b3 | ||
|
|
b0cf649c2d | ||
|
|
3499121f9c | ||
|
|
c43cacdc2f | ||
|
|
ead1e5e879 | ||
|
|
4c1f193d53 | ||
|
|
abaaea33d8 | ||
|
|
51257e47da | ||
|
|
fe42040d48 | ||
|
|
67ddd97f3d | ||
|
|
abea7bf329 | ||
|
|
bc3f49dc13 | ||
|
|
7565d04cf5 | ||
|
|
0d76eea1be | ||
|
|
9a2851a9d5 | ||
|
|
5e1612f204 | ||
|
|
316181b971 | ||
|
|
e753eed4d4 | ||
|
|
b52a07c7c4 | ||
|
|
f210c8b0db | ||
|
|
f865a9801f | ||
|
|
6a165e951b | ||
|
|
9322fbb234 | ||
|
|
4b7e2dce9d | ||
|
|
7bda2e987c | ||
|
|
e645b0abe1 | ||
|
|
f10a58ee1b | ||
|
|
f498f598bf | ||
|
|
4dfcb59b6f | ||
|
|
4506a3cb3e | ||
|
|
247559858f | ||
|
|
8a8cf3abc7 | ||
|
|
cf2a5ae4f9 | ||
|
|
0cf488b586 | ||
|
|
a019f22f47 | ||
|
|
9587e1b724 | ||
|
|
e5865a1446 | ||
|
|
7adbdf39ac | ||
|
|
57714efb40 | ||
|
|
dafb0906c3 | ||
|
|
96bb64aebb | ||
|
|
57254233e4 | ||
|
|
9ec4b35de9 | ||
|
|
9adbd77ce4 | ||
|
|
8957e8fff7 | ||
|
|
3aa0344c4a | ||
|
|
54de2c5fbf | ||
|
|
5b5f553d37 | ||
|
|
0e978b250b | ||
|
|
67e6888009 | ||
|
|
b9fe1f2057 | ||
|
|
debb91965b | ||
|
|
d0b3fefd57 | ||
|
|
5cc3623a2a | ||
|
|
d08f532a10 | ||
|
|
35b7e4a4de | ||
|
|
38b77d8817 | ||
|
|
3078f402bb | ||
|
|
f977daafbb | ||
|
|
aabd99e9eb | ||
|
|
e7dbab1822 | ||
|
|
cf6efd76f0 | ||
|
|
8a7922ffcc | ||
|
|
c7a8e84fd0 | ||
|
|
cb57bea862 | ||
|
|
b24ce80945 | ||
|
|
eb18397035 | ||
|
|
cfd1f951f0 | ||
|
|
b0e55eb25b | ||
|
|
37a86f3899 | ||
|
|
5700ae446f | ||
|
|
cb240f6a4b | ||
|
|
b405ffb8bd | ||
|
|
0fa08310e7 | ||
|
|
968d9819db | ||
|
|
54df69a2b3 | ||
|
|
88b3291785 | ||
|
|
c9c68f7d8c | ||
|
|
2d3f0baa51 | ||
|
|
bd4184252f | ||
|
|
f043dba23d | ||
|
|
f85c59b12c | ||
|
|
e6a267bd3f | ||
|
|
a532fb411b | ||
|
|
1f4ffdffc1 | ||
|
|
1053a9a761 | ||
|
|
34eedd1fe7 | ||
|
|
2c10e2b102 | ||
|
|
7f51bbd777 | ||
|
|
5c36e21948 | ||
|
|
a46443417e | ||
|
|
57055b3b55 | ||
|
|
559521c6df | ||
|
|
11e3b39486 | ||
|
|
1c50e397de | ||
|
|
05bea61a69 | ||
|
|
2a8b24074b | ||
|
|
99ead2c74c | ||
|
|
f557d2bee0 | ||
|
|
1f184a14ae | ||
|
|
8be1cd1090 | ||
|
|
5278f9a595 | ||
|
|
dd1a886b66 | ||
|
|
c3ec400ee7 | ||
|
|
7f95377ad7 | ||
|
|
903c553f94 | ||
|
|
0e7237c64a | ||
|
|
122af69cdc | ||
|
|
786d742e76 | ||
|
|
c4c46fd282 | ||
|
|
5a49c97e5d | ||
|
|
2cd195b343 | ||
|
|
36238267d0 | ||
|
|
52fe2f2c0d | ||
|
|
4bf0eba2f8 | ||
|
|
5130a74754 | ||
|
|
9d1bcfc0c6 | ||
|
|
15756a8977 | ||
|
|
0ab36bcbf6 | ||
|
|
36eef51e1c | ||
|
|
309c25a993 | ||
|
|
e61db5ed3f | ||
|
|
cc5ef21fd6 | ||
|
|
f1f925ecee | ||
|
|
ab422c0ebc | ||
|
|
05a13463f4 | ||
|
|
19238cc366 | ||
|
|
e3d4401d99 | ||
|
|
5da4efc2a3 | ||
|
|
c0c44156f1 | ||
|
|
8645833319 | ||
|
|
6a867d98eb | ||
|
|
58363ff4e4 | ||
|
|
2555aa72ca | ||
|
|
3b94fd4c4b | ||
|
|
95f500c32f | ||
|
|
870c2e8827 | ||
|
|
af2360648f | ||
|
|
bb95d40d6d | ||
|
|
55ea599fca | ||
|
|
fd5fdf3d13 | ||
|
|
ca129edce4 | ||
|
|
c1694a4b08 | ||
|
|
a348bbbebe | ||
|
|
e6b0a89d64 | ||
|
|
9a4821d1e3 | ||
|
|
71583a5b93 | ||
|
|
407ba34c72 | ||
|
|
fc4782a35b | ||
|
|
7a83b7db1b | ||
|
|
a827f4c4ce | ||
|
|
01c5794799 | ||
|
|
1c093cca2d | ||
|
|
a85b267872 | ||
|
|
5e0c1c9794 | ||
|
|
887df44359 | ||
|
|
a5fc162c71 | ||
|
|
9d3fc99d71 | ||
|
|
a871713907 | ||
|
|
2a19e5682a | ||
|
|
22d4fa73d6 | ||
|
|
2ed95c52ef | ||
|
|
73fb04aaf5 | ||
|
|
f97a7f426c | ||
|
|
c1189617f3 | ||
|
|
78d43c3cb6 | ||
|
|
86f9b6aa69 | ||
|
|
8df2bb8d2e | ||
|
|
33f30d925e | ||
|
|
c8c51245af | ||
|
|
db227679a8 | ||
|
|
02110de844 | ||
|
|
7d77a0eca6 | ||
|
|
0f3475128a | ||
|
|
e6e4714b02 | ||
|
|
d83ece397d | ||
|
|
a3c768bc6b | ||
|
|
a3d14003cb | ||
|
|
d451953e40 | ||
|
|
d505c42d15 | ||
|
|
8c92663824 | ||
|
|
10d86eb4d8 | ||
|
|
ee5e734f23 | ||
|
|
6b233b7da3 | ||
|
|
a140519374 | ||
|
|
2b118f2e42 | ||
|
|
be9d9fe377 | ||
|
|
d1345aafec | ||
|
|
d5932a7a57 | ||
|
|
5751beb72f | ||
|
|
02194a735a | ||
|
|
b5fc25b2f4 | ||
|
|
5a593b9ef3 | ||
|
|
b411914624 | ||
|
|
0f80b695e7 | ||
|
|
63c425f247 | ||
|
|
276627abcb | ||
|
|
34225d10aa | ||
|
|
69261c51b3 | ||
|
|
63d7ef7105 | ||
|
|
88a3e5b23f | ||
|
|
282c3aa00b | ||
|
|
19f3b03ac3 | ||
|
|
1b5efe1e5d | ||
|
|
2b5a15a178 | ||
|
|
a9f22d9375 | ||
|
|
f9b3514f2a | ||
|
|
be12fb507e | ||
|
|
b2dcd02ee4 | ||
|
|
5c23471254 | ||
|
|
6cb6f37871 | ||
|
|
67e284b680 | ||
|
|
1e5ba48fe0 | ||
|
|
8e6a27d7c1 | ||
|
|
d0edc0b484 | ||
|
|
ccd3d94ec0 | ||
|
|
98355d3850 | ||
|
|
0e53113b85 | ||
|
|
e9a3ed457e | ||
|
|
6001da30d8 | ||
|
|
c55f5e4b50 | ||
|
|
e983c0c2b9 | ||
|
|
532bf13057 | ||
|
|
de706e79de | ||
|
|
a19f337ebe | ||
|
|
041a904e44 | ||
|
|
131134d24b | ||
|
|
97fb5298a8 | ||
|
|
b580a177d4 | ||
|
|
a0b1ca5d9f | ||
|
|
3a291c56b4 | ||
|
|
ed41d5dbc5 | ||
|
|
628bd41740 | ||
|
|
de988f72c6 | ||
|
|
869c5ef544 | ||
|
|
17ee233e8b | ||
|
|
f2d1dffbe0 | ||
|
|
4008280a62 | ||
|
|
2e7235ec02 | ||
|
|
a4330f5048 | ||
|
|
f65aeff231 | ||
|
|
505243aff7 | ||
|
|
d0f2fc13ba | ||
|
|
53d51dcd38 | ||
|
|
c1aa0e49fe | ||
|
|
e1e72d4317 | ||
|
|
feec343cbc | ||
|
|
5cdc4233f3 | ||
|
|
1ae89d18cc | ||
|
|
eb19d00a1e | ||
|
|
e01ee10ef0 | ||
|
|
f4b3cb12e1 | ||
|
|
77ced60ad6 | ||
|
|
6fad90af6e | ||
|
|
3076318d7e | ||
|
|
9c99110738 | ||
|
|
1457d36997 | ||
|
|
43fb9296dd | ||
|
|
8bf744cc4a | ||
|
|
f1f5f070cf | ||
|
|
191a240d0d | ||
|
|
cafb2e355b | ||
|
|
c99c5751f8 | ||
|
|
435b7e56fa | ||
|
|
14f13fac53 | ||
|
|
f8b5037924 | ||
|
|
d2870cb02e | ||
|
|
8e43fc426c | ||
|
|
ba1b6e127a | ||
|
|
4f36a6958c | ||
|
|
df8f08b4ab | ||
|
|
fd5746d3fc | ||
|
|
6ab74fd51d | ||
|
|
245082ca1d | ||
|
|
845e58ea10 | ||
|
|
2a5dbc1ef7 | ||
|
|
02a1deb1e0 | ||
|
|
3ba8e5de10 | ||
|
|
819d1b4af7 | ||
|
|
cdd3d37376 | ||
|
|
d0bc01d618 | ||
|
|
ffab73334a | ||
|
|
83aeb8846c | ||
|
|
3a2087310b | ||
|
|
61b616701e | ||
|
|
bddf872906 | ||
|
|
48214a6fdb | ||
|
|
b7340856df | ||
|
|
2685854113 | ||
|
|
ad92f39469 | ||
|
|
d3fe041e01 | ||
|
|
f32bac5004 | ||
|
|
9a8e4ac105 | ||
|
|
78857e5c22 | ||
|
|
1bb828875d | ||
|
|
82aa67ffda | ||
|
|
7c44835772 | ||
|
|
8b9c63fe90 | ||
|
|
e77fcc8c16 | ||
|
|
08c12131d4 |
No files matched your search
@@ -1,166 +1,204 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding 6502/65C02 assembly in 64tass syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# 65(C)02 Assembly Coder Skill
|
||||
# 6502 Assembly Coder
|
||||
|
||||
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
|
||||
Use 64tass syntax, not ca65 or another assembler. Keep answers concise and
|
||||
practical. This applies to standalone `.asm` files and Prog8 `%asm {{ }}` or
|
||||
`asmsub` blocks.
|
||||
|
||||
## Debugging Generated Assembly
|
||||
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
|
||||
Reference documentation for the assembler is available locally in
|
||||
`~/Documents/prog8-dev-resources` (the 64tass manual, `64tass.md`). Search this
|
||||
folder first before looking online.
|
||||
|
||||
## Assembler: 64tass Syntax
|
||||
- **NOT ca65/cc65** or other assemblers. Key differences:
|
||||
- `.proc` / `.pend` for procedures (scoping)
|
||||
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
|
||||
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
|
||||
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
|
||||
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
|
||||
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
|
||||
- Example:
|
||||
```asm
|
||||
- dex ; backward label '-'
|
||||
bne - ; branch to most recent '-' (the dex above)
|
||||
ldx #5
|
||||
+ dex ; forward label '+'
|
||||
bne + ; branch to this same '+' (forward)
|
||||
sta $400
|
||||
+ lda #0 ; second '+'
|
||||
bne ++ ; branch to the '++' below
|
||||
rts
|
||||
++ inc $d020 ; third forward label
|
||||
```
|
||||
- Data directives: `.byte`, `.word`, `.dword`
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
- Zero-page variables defined with `=`
|
||||
- `.text` for inline string data
|
||||
## Syntax
|
||||
|
||||
## Instructions
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
|
||||
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
|
||||
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
|
||||
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
|
||||
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`.
|
||||
- Use `.byte`, `.word`, `.dword`, `.fill`, `.text`, and equates such as
|
||||
`name = value`.
|
||||
- Sections use `.section name` and `.send name`.
|
||||
- Procedures use `.proc` and `.pend`; labels beginning with `_` are local to a
|
||||
procedure.
|
||||
- Conditional assembly uses `.if`, `.elsif`, `.else`, and `.endif`.
|
||||
|
||||
## 6502 vs 65C02
|
||||
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
|
||||
- Check the target before using 65C02-specific instructions
|
||||
## Anonymous Labels
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
- **X register**: 8-bit, often used for indexing, loop counters
|
||||
- **Y register**: 8-bit, often used for indirect addressing index
|
||||
- **Processor Status (P)**: flags — carry (C), zero (Z), negative/N (sign bit 7), overflow (V), decimal (D), interrupt (I), break (B)
|
||||
- **No caller-saved vs callee-saved convention** — list all modified registers in `clobbers (A, X, Y)` when writing `asmsub`
|
||||
- The CPU stack (SP, $0100-$01FF) is limited (usually ~128 bytes free). Do not overflow it
|
||||
Anonymous label definitions are always a single `+` or `-` in column 1.
|
||||
References use repeated signs:
|
||||
|
||||
## Assembly within Prog8 Programs
|
||||
- `+` is the next upcoming `+` definition, `++` the second, `+++` the third.
|
||||
- `-` is the most recent `-` definition, `--` the preceding one, and so on.
|
||||
- Passing a `+` definition resets the forward-reference count.
|
||||
|
||||
### `%asm {{ }}` blocks
|
||||
- Embed arbitrary 64tass assembly directly in your Prog8 source
|
||||
- Access Prog8 symbols using their prefixed names (see below)
|
||||
- Can be placed inside subroutines or at block level
|
||||
```asm
|
||||
- dex
|
||||
bne - ; most recent '-' above
|
||||
ldx #5
|
||||
+ dex ; first upcoming '+'
|
||||
bne + ; this '+'
|
||||
sta $400
|
||||
+ lda #0 ; second '+' definition
|
||||
bne ++ ; next '+' after this one
|
||||
rts
|
||||
+ inc $d020 ; third '+' definition
|
||||
```
|
||||
|
||||
### `asmsub` (assembly subroutine)
|
||||
- For kernel (ROM) routines or low-level assembly
|
||||
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
|
||||
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
|
||||
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
|
||||
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
|
||||
## CPU Differences
|
||||
|
||||
- `cx16` is the only Prog8 target that supports 65C02 instructions such as
|
||||
`stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `wai`, `stp`, and
|
||||
`bit #imm`. C64, C128, and PET32 use the original 6502.
|
||||
- Rockwell instructions (`rmb`, `smb`, `bbr`, `bbs`) are not available.
|
||||
- Use an explicit `a` for accumulator forms such as `rol a`, `lsr a`, `inc a`,
|
||||
and `dec a`.
|
||||
- Branches are relative. Prog8's assembler invocation enables `--long-branch`
|
||||
for out-of-range branches.
|
||||
- `lda`, `ldx`, and `ldy` do not affect carry. `cmp` sets carry for
|
||||
`register >= operand`.
|
||||
- On NMOS 6502, `jmp ($xxff)` wraps the high-byte fetch within the page.
|
||||
- `BRK` skips the byte after its opcode. The byte is commonly a handler
|
||||
signature.
|
||||
- `bit` memory forms set N and V from the operand and Z from `A AND operand`.
|
||||
65C02 `bit #imm` sets only Z.
|
||||
- Decimal mode can persist into NMOS IRQ handlers. Use `cld` at handler entry
|
||||
and explicitly control D before `adc` or `sbc`.
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
`asmsub` parameter names are documentation only. Use the actual registers or
|
||||
define assembly aliases, and list every modified register in `clobbers`.
|
||||
|
||||
Example:
|
||||
```prog8
|
||||
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
|
||||
asmsub increment(uword value @R0) clobbers (A, X) {
|
||||
%asm {{
|
||||
x1 = cx16.r0
|
||||
x2 = cx16.r1
|
||||
lda x1 ; use alias, not "_x1"
|
||||
lda cx16.r0L
|
||||
clc
|
||||
adc #1
|
||||
sta cx16.r0L
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
### `asmsub` parameter annotation reference
|
||||
| Annotation | Register | Size |
|
||||
|------------|----------|------|
|
||||
| `@A` | Accumulator | 8-bit |
|
||||
| `@X` | X register | 8-bit |
|
||||
| `@Y` | Y register | 8-bit |
|
||||
| `@AX` | A (low) + X (high) | 16-bit |
|
||||
| `@AY` | A (low) + Y (high) | 16-bit |
|
||||
| `@R0`-`@R15` | cx16 virtual registers | 16-bit each |
|
||||
| `@FAC1`/`@FAC2` | Floating-point accumulators | 5-byte float |
|
||||
| `@Pc` | Carry flag | bool |
|
||||
| `@Pz` | Zero flag | bool |
|
||||
Annotations include `@A`, `@X`, `@Y`, `@AX`, `@AY`, `@R0`-`@R15`, `@FAC1`,
|
||||
`@FAC2`, `@Pc` (carry), and `@Pz` (zero). Unmapped parameters are accessed as
|
||||
`p8v_name`.
|
||||
|
||||
## Accessing Prog8 Symbols from Assembly
|
||||
All Prog8 symbols are prefixed when accessed from assembly:
|
||||
Prog8 symbols use these prefixes: `p8v_` variables and parameters, `p8s_`
|
||||
subroutines, `p8b_` blocks, `p8c_` constants, `p8l_` labels, and `p8t_` struct
|
||||
types. Fully qualified names may look like
|
||||
`p8b_main.p8s_helper.p8v_local`. `%option no_symbol_prefixing` disables the
|
||||
prefixes. Split word arrays use `_lsb` and `_msb` symbols.
|
||||
|
||||
| Prefix | Refers to | Example |
|
||||
|--------|-----------|---------|
|
||||
| `p8v_` | Variables, parameters | `p8v_myvar` |
|
||||
| `p8s_` | Subroutines | `p8s_mysub` |
|
||||
| `p8b_` | Blocks | `p8b_myblock` |
|
||||
| `p8c_` | Constants, enum members | `p8c_myconst`, `p8c_MyEnum_Member` |
|
||||
| `p8l_` | Labels | `p8l_mylabel` |
|
||||
| `p8t_` | Struct types | `p8t_MyStruct` |
|
||||
| `p8_` | Other symbols | |
|
||||
## Zeropage and Memory
|
||||
|
||||
- **Fully qualified**: e.g., `p8b_myblock.p8v_myvar`, `p8b_myblock.p8s_mysub.p8v_localvar`
|
||||
- Within a `.proc` (subroutine) in your assembly, short names often work (assembler scoping)
|
||||
- **`%option no_symbol_prefixing`**: disables all prefixes. Stdlib modules (`cbm`, `cx16`, `txt`) use this — you can write `cbm.CHROUT` directly
|
||||
- **Split word arrays**: two separate byte arrays — append `_lsb` and `_msb` to the name: `p8v_myarray_lsb`, `p8v_myarray_msb`
|
||||
- Never hardcode zeropage addresses. Use `P8ZP_SCRATCH_B1`,
|
||||
`P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, and
|
||||
`P8ZP_SCRATCH_PTR`, or the appropriate `cx16.r0`-`cx16.r15` symbols.
|
||||
- Scratch variables are not necessarily consecutive. CX16 virtual registers
|
||||
are consecutive and have `L` and `H` byte names.
|
||||
- Allocate additional temporary storage in BSS.
|
||||
- The hardware stack occupies `$0100-$01ff` and is limited. Do not overflow
|
||||
it.
|
||||
- Prog8 words are little-endian: load the low byte first, then the high byte.
|
||||
|
||||
## Zeropage Usage
|
||||
- Do NOT use arbitrary zeropage locations. Only use these predefined scratch variables:
|
||||
- `P8ZP_SCRATCH_B1` (byte)
|
||||
- `P8ZP_SCRATCH_REG` (byte)
|
||||
- `P8ZP_SCRATCH_W1` (word)
|
||||
- `P8ZP_SCRATCH_W2` (word)
|
||||
- `P8ZP_SCRATCH_PTR` (word)
|
||||
- On CX16: virtual registers `cx16.r0`-`cx16.r15` are in zeropage (and their low/high bytes: `cx16.r0L`, `cx16.r0H`, etc.)
|
||||
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
|
||||
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
|
||||
- For additional temporary storage, allocate regular variables in BSS
|
||||
|
||||
## Common 6502 Patterns
|
||||
|
||||
### Looping (downto with BNE)
|
||||
```asm
|
||||
ldx #count
|
||||
loop ; do work here
|
||||
dex
|
||||
bne loop ; loop while X != 0
|
||||
```
|
||||
|
||||
### Indirect indexed read (table of data)
|
||||
```asm
|
||||
ldy #index
|
||||
lda (ptr),y ; read byte at address stored in zp ptr + Y
|
||||
```
|
||||
|
||||
### 16-bit arithmetic (word add)
|
||||
```asm
|
||||
clc
|
||||
lda word1_lo
|
||||
adc word2_lo
|
||||
sta result_lo
|
||||
lda word1_hi
|
||||
adc word2_hi
|
||||
sta result_hi
|
||||
lda word1
|
||||
adc word2
|
||||
sta result
|
||||
lda word1+1
|
||||
adc word2+1
|
||||
sta result+1
|
||||
```
|
||||
|
||||
### Calling a Prog8 subroutine from assembly
|
||||
## Common Patterns
|
||||
|
||||
```asm
|
||||
jsr p8s_myblock.p8s_mysub
|
||||
ldy #index
|
||||
lda (ptr),y
|
||||
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
### Reading a word variable
|
||||
```asm
|
||||
lda p8v_myword ; loads LSB
|
||||
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
|
||||
Avoid self-modifying code unless it is explicitly required. It cannot run
|
||||
from ROM and complicates debugging and tooling. Prefer lookup tables, RAM
|
||||
vectors, or alternative algorithms.
|
||||
|
||||
## Reference
|
||||
|
||||
### Primary: 6502 Family CPU Reference
|
||||
|
||||
<https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2> is the reference to reach for.
|
||||
It covers the whole 6502 family and lets you **select the CPU variant**, which matters
|
||||
because the parts genuinely differ. The variants are `6502`, `6502rorbug`, `65dtv02`,
|
||||
`65c02`, `r65c02` (Rockwell), `65c02s`, `65ce02` and `65c816`. Change the `cpu=` query
|
||||
parameter to switch; for Prog8, use `cpu=65c02` when working on cx16 and `cpu=6502` for
|
||||
c64, c128 and pet32.
|
||||
|
||||
Per instruction it gives the operation, the opcodes, the byte length, the cycle count
|
||||
and which status flags are affected, with tabs for opcodes, instructions, addressing
|
||||
modes and a full opcode table (switchable between 4-4 and 3-3-2 layout). It can also
|
||||
show the NMOS undocumented opcodes, and each part lists what it is based on, so a
|
||||
variant's inherited instructions are visible.
|
||||
|
||||
Cycle counts use a compact notation worth knowing: a trailing `t` means +1 if a branch
|
||||
is taken, `p` means +1 if a page is crossed, and `d` means +1 when the D (decimal) flag
|
||||
is set. So `d0 2+t+p` for `bne` is 2 cycles, 3 if taken, 4 if taken across a page.
|
||||
|
||||
Consult it whenever an exact figure is needed rather than recalled: the byte length or
|
||||
cycle cost of an instruction, which flags it clobbers, whether an addressing mode exists
|
||||
on the target CPU, or whether an instruction belongs to a different part at all. Typical
|
||||
reasons are costing a peephole or code-size tradeoff, checking a hand-written routine,
|
||||
and confirming a mnemonic is valid before using it.
|
||||
|
||||
Do not assume a timing from memory or from another part. Cycle counts are exactly the
|
||||
kind of detail that is misremembered, and the difference matters when it is the basis of
|
||||
an optimization claim. Read the number off the reference for the specific CPU being
|
||||
targeted.
|
||||
|
||||
### Secondary: WDC 65C02 reference
|
||||
|
||||
<https://cx16.dk/65c02/reference.html> is a 65C02-only reference. It is worth having
|
||||
because it states each instruction's effect in a single formula (for example
|
||||
`ADC: A,Z,C,N = A+M+C`), which makes the flag behaviour quick to check, and it lists
|
||||
every status bit per instruction including the ones not affected. Use it for flag
|
||||
semantics; use the primary reference for timings and for anything CPU-specific, since
|
||||
it covers the 65C02 only and will not answer a question about the NMOS 6502.
|
||||
|
||||
### Neither reference covers the assembler
|
||||
|
||||
Both document the CPU, not 64tass. For whether 64tass accepts a mnemonic or a given
|
||||
syntax, assemble it: an instruction outside the selected CPU set fails with
|
||||
`error: general syntax` rather than being silently ignored. Note also that 64tass has
|
||||
no `w65c02s`; its CPU selection is `--m65xx` (default), `--m65c02` and `--m65ce02`, and
|
||||
Prog8 emits `.cpu 'w65c02'`.
|
||||
|
||||
## Tools
|
||||
|
||||
Prog8 invokes 64tass with `--ascii --case-sensitive --long-branch -Wall` plus
|
||||
the target output option such as `--cbm-prg`. For manual assembly:
|
||||
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg \
|
||||
-o myprogram.prg myprogram.asm
|
||||
```
|
||||
|
||||
For debugging, add `--vice-labels --labels=labels.txt` and/or
|
||||
`--list=listing.txt`.
|
||||
|
||||
To check an instruction's encoding or size, assemble it in isolation and read the
|
||||
output bytes. The 2-byte load address comes first, so `stx $7b` / `ldy $7b` below
|
||||
shows as `86 7b a4 7b` - two instructions, four bytes:
|
||||
|
||||
```bash
|
||||
printf '.cpu %s\n* = $0801\nlbl\tstx\t$7b\n\tldy\t$7b\n' "'w65c02'" > sz.asm
|
||||
64tass --ascii --case-sensitive --cbm-prg -o sz.prg sz.asm && xxd sz.prg
|
||||
```
|
||||
|
||||
Note that a 64tass label takes no trailing colon; `lbl:` is a syntax error reported as
|
||||
`error: label required`.
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
name: m68k-coder
|
||||
description: Writing or understanding Motorola 68000/68020 assembly in vasm mot syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# M68K Assembly Coder
|
||||
|
||||
Use Motorola syntax for `vasm`, not Devpac or other assemblers. Keep answers
|
||||
concise and practical.
|
||||
|
||||
Reference documentation for the assembler and linker is available locally in
|
||||
`~/Documents/prog8-dev-resources` (the vasm and vlink manuals). Search this
|
||||
folder first before looking online.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Use `.b`, `.w`, and `.l` for byte, word, and long operations. Do not rely on
|
||||
default sizes.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`, and characters use
|
||||
`#'A'`.
|
||||
- Local labels start with `.`. Equates use `name = value`.
|
||||
- Data directives are `dc.b`, `dc.w`, `dc.l`; allocation directives are
|
||||
`ds.b`, `ds.w`, `ds.l`.
|
||||
- Sections use forms such as `SECTION .text,code` and `SECTION .bss,bss`.
|
||||
|
||||
```asm
|
||||
moveq #0,d0
|
||||
move.w value,d0
|
||||
add.l d1,d0
|
||||
.loop
|
||||
dbra d0,.loop
|
||||
```
|
||||
|
||||
## FPU Size Suffixes
|
||||
|
||||
FPU instructions that access memory **must** include a size suffix: `.s`
|
||||
(single, 4 bytes), `.d` (double, 8 bytes), or `.x` (extended, 12 bytes).
|
||||
Without a suffix, vasm defaults to `.x`, reading 12 bytes and corrupting
|
||||
results when the operand is smaller.
|
||||
|
||||
**Use `.s` with memory operands:**
|
||||
```asm
|
||||
fmove.s value(pc),fp0 ; load float from memory
|
||||
fcmp.s other(pc),fp0 ; compare float in memory
|
||||
ftst.s value(pc) ; test float in memory
|
||||
```
|
||||
|
||||
**Omit the suffix for register-to-register operations:**
|
||||
```asm
|
||||
fadd fp1,fp0 ; fp0 = fp0 + fp1
|
||||
fcmp fp1,fp0 ; compare fp0 vs fp1
|
||||
ftst fp0 ; test fp0
|
||||
fintrz fp0,fp0 ; truncate fp0
|
||||
```
|
||||
|
||||
The FPU registers are always 80-bit internally, so no size suffix is needed
|
||||
when both operands are registers.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Registers `D0-D7` and `A0-A7` are 32-bit; `A7` is the stack pointer.
|
||||
- Pointers are 32-bit and must normally be accessed with `.l`.
|
||||
- Memory is big-endian: `$1234` is stored as `$12,$34` and a long's low byte
|
||||
is at offset `+3`.
|
||||
- Word and long accesses must be even-aligned on 68000. 68020 permits
|
||||
misalignment but it is slower.
|
||||
- **Byte `A7` auto-update steps by 2, not 1.** If A7 (== SP, the stack pointer) is the address
|
||||
register and the operand length for post-increment or post-decrement is one byte, A7 is
|
||||
decremented/incremented by 2 instead of 1, to maintain a word-aligned stack. This is the
|
||||
specified behaviour of all the 680x0, not a 68000 quirk, so it is identical everywhere and
|
||||
safe to rely on. Word and long operands step by 2 and 4.
|
||||
```asm
|
||||
move.b d0,-(sp) ; A7 steps by 2, so SP stays word-aligned
|
||||
...
|
||||
addq.l #2, sp ; and the matching pop is #2, not #1
|
||||
```
|
||||
Do not "fix" this to an explicit `subq.l #1,sp`: that leaves SP odd and cause crash.
|
||||
- The 68000 (`amiga500`) has only word multiply/divide and no scaled indexing,
|
||||
bit-field, or 32-bit multiply/divide instructions. The 68020 (`qemu68k`)
|
||||
adds those features and `extb.l`.
|
||||
- On 68000, sign-extend a byte with `ext.w` followed by `ext.l`.
|
||||
- `move.b` and `move.w` to a data register do not clear its upper bits. Like
|
||||
`move.w` only writes the lower 16 bits, `move.b` only writes the lower 8 bits;
|
||||
both leave all other bits of the register untouched. So zero-extending a byte
|
||||
to long requires `moveq #0,d0` first, then `move.b src,d0`, then `move.l d0,dst`
|
||||
(or mask with `and.l #$ff` / `and.l #$ffff`). Relying on `move.b` to clear the
|
||||
upper bits is a common bug that corrupts values.
|
||||
- `movea` does not set condition codes; `move` does. Keep the stack word
|
||||
aligned and push words or longs, never bytes.
|
||||
|
||||
## Registers and Calls
|
||||
|
||||
- Return values use `D0` (primary), `D1` and `D2` (additional), or `A0` for a
|
||||
pointer.
|
||||
- `D0-D1` and `A0-A1` are scratch registers. Preserve registers required by
|
||||
the surrounding calling convention, especially `D2-D7` and `A2-A6`.
|
||||
- Never clobber `A5`: on every M68K target it is the Prog8 stack frame pointer
|
||||
(locals at negative offsets, parameters at positive offsets). Overwriting it
|
||||
corrupts the frame of the running subroutine and breaks unwinding.
|
||||
- Never clobber `A6` on the Amiga targets (`amiga500`, `amiga1200`): it holds
|
||||
the AmigaOS library base pointer that library calls depend on. Prog8 itself
|
||||
leaves `A6` free for that reason.
|
||||
- `move.l d0,-(sp)` pushes and `move.l (sp)+,d0` pops.
|
||||
|
||||
### Prog8 M68K stack ABI
|
||||
|
||||
- Normal framed subroutines use `link a5,#-N` and `unlk a5`; locals are at
|
||||
negative `A5` offsets and incoming parameters are at positive offsets.
|
||||
- Arguments use word-packed slots of natural width: byte, bool, word and uword
|
||||
arguments take a 2-byte slot; long, pointer and float arguments take a 4-byte
|
||||
slot. The caller pushes arguments left-to-right (a byte sits at the base byte
|
||||
of its 2-byte slot, the second byte is garbage; word values fill their slot)
|
||||
and removes the complete argument area after the call. The area
|
||||
size is always even, so the stack stays word-aligned; slot bases accumulate from
|
||||
the rightmost argument at `8(a5)` according to each argument's width.
|
||||
- A normal call preserves the caller's frame. A `goto` or conditional branch
|
||||
leaving the current subroutine must execute `unlk a5` on the taken path before
|
||||
transferring control.
|
||||
- The built-in Prog8 `call(address)` is a zero-argument indirect call. It does
|
||||
not marshal stack arguments. `asmsub` and `extsub` keep their explicit
|
||||
register-annotated ABI, separate from normal framed calls.
|
||||
- Push narrow arguments into their own 2-byte slots, each with one instruction: a
|
||||
byte uses `move.b ..., -(sp)` (A7 byte predecrement steps by 2, reserving the
|
||||
slot; the byte lands at the slot's base byte), a word uses a plain
|
||||
`move.w ..., -(sp)`. Never push a narrow value where the callee expects a
|
||||
wide (4-byte) argument: the slot sizes must line up exactly.
|
||||
Wide arguments (`move.l ..., -(sp)` / `fmove.s fp0,-(sp)`) push in one step.
|
||||
Use `lea N(sp),sp` for the cleanup when the call returns a status flag
|
||||
(`addq` clobbers CCR).
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
Assembly can appear in `%asm {{ }}` blocks or `asmsub` routines. `asmsub`
|
||||
parameter names are documentation only: use the actual annotated registers.
|
||||
Declare every modified register in `clobbers`.
|
||||
|
||||
```prog8
|
||||
asmsub add100(uword value @D0) -> uword @D0 clobbers (D0) {
|
||||
%asm {{
|
||||
add.w #100,d0
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization and CLI argument handling, causing programs to crash on
|
||||
start.
|
||||
|
||||
Supported annotations include `@D0`-`@D7`, `@A0`-`@A6`, virtual `@R0`-`@R15`,
|
||||
floating-point `@FP0`-`@FP7`, and flags `@Pc` and `@Pz`.
|
||||
|
||||
Prog8 assembly symbols are prefixed:
|
||||
|
||||
| Prefix | Symbol |
|
||||
| --- | --- |
|
||||
| `p8v_` | variable or parameter |
|
||||
| `p8s_` | subroutine |
|
||||
| `p8b_` | block |
|
||||
| `p8c_` | constant or enum member |
|
||||
| `p8l_` | label |
|
||||
| `p8t_` | struct type |
|
||||
|
||||
Use fully qualified names when needed, such as
|
||||
`p8b_main.p8v_value`. `%option no_symbol_prefixing` disables prefixes.
|
||||
|
||||
```asm
|
||||
move.l p8v_pointer,a0
|
||||
move.w p8v_word,d0
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
## Useful Patterns
|
||||
|
||||
```asm
|
||||
lea source,a0
|
||||
lea dest,a1
|
||||
.copy
|
||||
move.b (a0)+,(a1)+
|
||||
bne.s .copy
|
||||
```
|
||||
|
||||
Use `moveq` for small constants, `addq`/`subq` for constants 1 through 8, and
|
||||
`lea` for address arithmetic. Check divisors for zero and division overflow.
|
||||
|
||||
## Tools
|
||||
|
||||
For QEMU 68k, Prog8 invokes `vasmm68k_mot` with `-m68020 -m68881 -Felf
|
||||
-opt-speed -warnunaligned -ldots -spaces`, then links with `vlink`. For raw
|
||||
binary output use `-Fbin` and `-join=0x<addr>`. Add `-L listing.txt` when a
|
||||
listing is needed.
|
||||
|
||||
Prog8 runs `amiga500` programs with `vamos --cpu 68000` and `qemu68k` programs
|
||||
with `qemu-system-m68k -M virt -cpu m68020 -m 1M -kernel program.elf -nographic`.
|
||||
+143
-196
@@ -1,221 +1,168 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding Prog8 programs and Prog8 IR
|
||||
---
|
||||
|
||||
# Prog8 Coder Skill
|
||||
# Prog8 Coder
|
||||
|
||||
You are working with **Prog8** source code (`.p8` files) or its Intermediate Representation (`.p8ir` files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a `virtual` target for testing.
|
||||
You are an expert Prog8 assistant. Keep answers concise and practical. This
|
||||
skill covers `.p8` source and `.p8ir` intermediate representation for the
|
||||
6502, M68000, and virtual targets.
|
||||
|
||||
Follow ALL the rules below carefully.
|
||||
Various reference documentation is available locally in
|
||||
`~/Documents/prog8-dev-resources`:
|
||||
|
||||
## General & Setup
|
||||
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
|
||||
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
|
||||
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
|
||||
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
|
||||
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
|
||||
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
|
||||
- **Compilation outputs**: `*.prg` (program binary), `*.asm` (assembly listing), `*.list` (full listing), `*.p8ir` (IR for VM), `*.vice-mon-list` (Vice debug symbols)
|
||||
- `.p8ir` files contain the **Intermediate Representation** — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via `prog8c -vm file.p8ir`. Useful for debugging the compiler's code generation path without involving 6502 assembly.
|
||||
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs)
|
||||
- **TODO list**: Check `docs/source/todo.rst` to see what features are NOT yet implemented
|
||||
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
|
||||
- **`sys` module**: always available, no import needed
|
||||
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
|
||||
Prog8 reference documentation, emulator manuals such as VICE and the X16
|
||||
emulator, the 64tass/vasm/vlink assembler and linker docs, X16 hardware and register specs,
|
||||
the Amiga NDK (docs and includes), and other resources.
|
||||
Search in this folder first before looking online!
|
||||
|
||||
## Datatypes & Variables
|
||||
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
|
||||
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
|
||||
- **float requires `%import floats`** at top of file, else compiler errors
|
||||
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
|
||||
- `memory(name, size)` returns a `uword` address to a statically reserved block of memory
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable)
|
||||
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
|
||||
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
|
||||
- **No call stack**: all variables statically allocated. No recursion without manual stack management
|
||||
- Variables zero-initialized (globals at start, locals on subroutine entry)
|
||||
- `@shared` marks variables as "used by external code" (assembly), prevents optimization
|
||||
- `@zp`/`@requirezp`: use sparingly — only for pointers (limited zeropage space)
|
||||
- Pointer-like typed pointers (`^^type`) support C-style scaled arithmetic; `uword` pointers always treat element as 1 byte
|
||||
- `&` = untyped address (uword); `&&` = typed pointer
|
||||
- Available zeropage scratch: `P8ZP_SCRATCH_B1`, `P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, `P8ZP_SCRATCH_PTR` — and cx16 virtual registers R0-R15 on all targets
|
||||
- Virtual registers (`cx16.r0`–`cx16.r15`): global 16-bit, NOT preserved across calls. R12-R15 may be clobbered by long ops. Save/restore in IRQs with `cx16.save_virtual_registers()`/`cx16.restore_virtual_registers()`
|
||||
- Math performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
|
||||
|
||||
## Recursion & Stack Management
|
||||
No call stack for variable storage — recursion overwrites locals. To handle it:
|
||||
1. **CPU hardware stack**: `push()`/`pushw()`/`pushl()`/`pushf()` and `pop()`/`popw()`/`popl()`/`popf()`. Save/restore locals around recursive calls
|
||||
2. **Software stacks**: `buffers.stack` (uword) / `buffers.smallstack` (ubyte). Both provide `push_b()`/`push_w()` and `pop_b()`/`pop_w()`
|
||||
3. **Iterative rewrite (preferred)**: Many recursive algorithms work as `repeat` loops with explicit bounds — avoids all stack overhead
|
||||
## Essentials
|
||||
|
||||
## Strings, Arrays & Pointers
|
||||
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
|
||||
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
|
||||
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
|
||||
- **No const pointers** or pointer-to-pointer currently supported
|
||||
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
|
||||
- **Pre-allocate buffers**: `str buffer = "." * 50` (empty `""` allocates nothing — `strings.append()` will fail)
|
||||
- **No reassignment**: can't `buffer = "new text"` after declaration. Use `strings.copy()`/`strings.ncopy()`
|
||||
- String concat is expensive on 6502. Prefer separate prints over concatenation
|
||||
- **Efficient buffer iteration**: prefer `ptr++` + `@(ptr)` over `@(buffer + offset)`. Exception: if offset is a `ubyte` (≤ 255), `buffer[offset]` works fine
|
||||
- `@(ptr)` = peek/poke byte. For words: `peekw`/`pokew`, longs: `peekl`/`pokel`, floats: `peekf`/`pokef`, bools: `peekbool`/`pokebool`
|
||||
- Use `len(array)` instead of hardcoded sizes
|
||||
- Array indexing is 0-based: `arr[0]` is first element
|
||||
- Static memory only — real dynamic allocation impossible, but can emulate with a simple arena allocator over a `memory()` slab
|
||||
- A program normally has a `main` block with a parameterless `start` entry
|
||||
subroutine. Other blocks and subroutines may be added.
|
||||
- `%import modulename` imports a module without aliases. Use its defined prefix,
|
||||
such as `%import textio` followed by `txt.print(...)`.
|
||||
- `sys` is always available and needs no import.
|
||||
- `.p8ir` is target-independent IR and can be run with
|
||||
`prog8c -vm program.p8ir`.
|
||||
- Use `%zeropage basicsafe` in test programs when a clean return is needed.
|
||||
- Find library routines with `prog8c -libsearch <regex>` or extract them with
|
||||
`prog8c -libdump <dir>`.
|
||||
- Options accept single-dash or double-dash (`-target` == `--target`). Use
|
||||
`-out <dir>` to place output in a directory, or `-o <file>` to name the exact
|
||||
final program artifact (all other output files then land beside it).
|
||||
|
||||
## Logic & Control Flow
|
||||
- Logical operators (short-circuit, bool only): `and`, `or`, `xor`, `not`
|
||||
- In `a and b`: if `a` is false, `b` is NOT evaluated
|
||||
- In `a or b`: if `a` is true, `b` is NOT evaluated
|
||||
- Important when `b` has side effects
|
||||
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
|
||||
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
|
||||
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
|
||||
- Use `when` with choice blocks instead of multiple `if`
|
||||
- If-expressions for simple value assignments based on a choice
|
||||
- `defer` defers statement execution until scope exit
|
||||
- `goto`, labels, jump lists allowed
|
||||
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
|
||||
For CX16 emulator programs, use `%encoding iso`, call `txt.iso()` in `start`,
|
||||
and finish with `sys.poweroff_system()`. CBM targets use PETSCII by default;
|
||||
avoid uppercase in test output unless intentional graphics. The virtual target
|
||||
uses ISO when configured with `%encoding iso` and `txt.iso()`.
|
||||
|
||||
## Loop Constructs
|
||||
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
|
||||
On any CBM machine with PETSCII (this includes the Commander X16), files
|
||||
on disk must only consist of 'safe' characters (`a-z`, `0-9`, `.-+!%`).
|
||||
Do NOT mix casing in filenames; in Prog8 source code, filenames should
|
||||
usually be written in *lowercase* letters. Other characters (such as
|
||||
`_` or uppercase mixed with lowercase) can make kernal file operations
|
||||
fail, depending on the drive/host layer.
|
||||
|
||||
### `for` loop — iterate over a range or array
|
||||
- Loop variable **must be declared separately** before the `for` statement
|
||||
- Works with `ubyte`, `byte`, `uword`, `word`, `long`, pointer types (NOT `float`)
|
||||
- Iterates over ranges (`start to end`), descending ranges (`start downto end`), or arrays/strings
|
||||
- Optional `step <constant>` for non-unit step sizes
|
||||
- Loop variable value **after the loop is undefined** — don't rely on it
|
||||
- Descending loops with `downto` usually produce more efficient 6502 code
|
||||
```
|
||||
ubyte i
|
||||
for i in 20 to 155 {
|
||||
; body
|
||||
break ; exit loop
|
||||
continue ; next iteration
|
||||
}
|
||||
Do not use `%option no_sysinit` on `amiga500`, `amiga1200`, or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization (DOS, graphics, intuition, timer) and CLI argument
|
||||
handling, causing programs to crash on start. It is appropriate for library
|
||||
modules, IRQ handlers, boot stubs, or code that deliberately avoids those
|
||||
libraries.
|
||||
|
||||
; descending
|
||||
for i in 155 downto 20 {}
|
||||
for i in 155 to 20 step -1 {}
|
||||
## Types and Memory
|
||||
|
||||
; iterate over array elements
|
||||
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
|
||||
uword num
|
||||
for num in fib {
|
||||
...
|
||||
}
|
||||
- Primitive types include `bool`, `byte`, `ubyte`, `word`, `uword`, `long`,
|
||||
`float`, `str`, and `pointer`.
|
||||
- `pointer` is target-sized: 16-bit on 6502 targets, 32-bit on `amiga500`,
|
||||
`qemu68k`, and `virtual`. Use it for portable addresses, not `uword` or
|
||||
`long` chosen by assumption.
|
||||
- M68000 targets are big-endian and use 32-bit pointers. Load the `m68k-coder`
|
||||
skill for assembly details.
|
||||
- `float` requires `%import floats`.
|
||||
- On 6502 and virtual targets, variables are statically allocated and
|
||||
zero-initialized. There is no normal call stack for locals, so recursion
|
||||
overwrites locals unless an explicit hardware or software stack is used.
|
||||
Iterative rewrites are preferred. Arrays are cleared once at startup here,
|
||||
not on every subroutine entry.
|
||||
- On the M68000 targets (`amiga500`, `amiga1200`, `qemu68k`) ordinary subroutine
|
||||
locals and parameters live in an activation record on the CPU stack, so true
|
||||
recursion and re-entrancy work. `link a5,#-N` / `unlk a5`, frame pointer `A5`,
|
||||
locals at negative offsets, parameters at positive offsets. Block-level
|
||||
variables, globals, `@shared`, memory slabs, and objects needing a permanent
|
||||
external address stay static. Clean locals are zeroed on every call
|
||||
including arrays, so the compiler warns for framed arrays over 8 bytes; use
|
||||
`@dirty` when you assign first, otherwise you read stack garbage.
|
||||
- Normal M68K calls use word-packed argument slots: byte/bool/word/uword arguments
|
||||
take a 2-byte slot, long/pointer/float take 4 bytes. The caller pushes arguments
|
||||
left-to-right and removes the complete argument area after the call. Slot bases
|
||||
accumulate right-to-left starting at positive `A5` offset 8, so a parameter's
|
||||
offset depends on the widths of the parameters to its right; locals use negative
|
||||
offsets. See `docs/source/technical.rst` for the full convention and static
|
||||
fallback rules.
|
||||
- `memory(name, size)` reserves static memory and returns a `pointer`.
|
||||
- `str` and arrays have compile-time byte budgets. On 6502 targets the usual
|
||||
limit is 256 bytes; M68000 targets allow up to 32768 bytes. Use `memory()`
|
||||
for larger data.
|
||||
- Word arrays are split into LSB/MSB arrays by default. Use `@nosplit` when
|
||||
contiguous storage is required.
|
||||
- Variable tags follow the type and array dimensions, before the name:
|
||||
|
||||
```prog8
|
||||
ubyte[8] @shared vera_storage
|
||||
uword @requirezp address
|
||||
```
|
||||
|
||||
### `while` loop — repeat while condition is true
|
||||
```
|
||||
while condition {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
}
|
||||
```
|
||||
Use `@shared` for values accessed externally by assembly. Use `@zp` and
|
||||
`@requirezp` sparingly because zeropage is limited. Never assume virtual
|
||||
registers survive calls. Long operations may clobber `R12-R15`.
|
||||
|
||||
### `do`-`until` loop — always executes body at least once
|
||||
```
|
||||
do {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
} until condition
|
||||
```
|
||||
## Syntax Pitfalls
|
||||
|
||||
### `repeat` loop — repeat a fixed number of times (most efficient)
|
||||
- Most efficient code generation — prefer over `for` when loop variable not needed
|
||||
- Omit count for infinite loop (still supports `break`)
|
||||
```
|
||||
repeat 15 {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
}
|
||||
- Hex uses `$FF`, binary uses `%1010`, and casts use `expression as type`.
|
||||
- There is no automatic type widening in expression results: `byte * byte`
|
||||
remains a byte. Cast explicitly when a wider result is needed. Assignments,
|
||||
call arguments, and mixed-type expressions do widen implicitly
|
||||
(`ubyte`/`byte` to `word`/`uword` to `long`); only same-width signedness
|
||||
reinterpretation (`uword` to `word` and back) needs an explicit `as` cast.
|
||||
- `&` is an untyped address and `&&` is a typed pointer. Complex pointer field
|
||||
assignments may require `ptr^^.field`.
|
||||
- `and`, `or`, `xor`, and `not` are logical operators. Use `&`, `|`, `^`, and
|
||||
`~` for bitwise operations.
|
||||
- `and` and `or` short-circuit, so the right operand may not be evaluated.
|
||||
- There is no `elif`, bare block, or semicolon statement separator. Semicolon
|
||||
starts a comment. Prefer one statement per line and four-space indentation.
|
||||
- `defer` executes registered statements in reverse registration order.
|
||||
- Array indexing starts at zero. Use `len(array)` rather than hardcoded sizes.
|
||||
- There is no function overloading. Call type-specific library routines such as
|
||||
`txt.print_ub` or `txt.print_w`.
|
||||
|
||||
; infinite:
|
||||
repeat {
|
||||
; body
|
||||
break if x==5
|
||||
}
|
||||
```
|
||||
## Control Flow
|
||||
|
||||
### `unroll` loop — compile-time code duplication
|
||||
- Not a real loop — duplicates body N times at compile time
|
||||
- No `break`/`continue` allowed
|
||||
- Only simple statements (assignments, calls) in body
|
||||
- Constant iteration count required
|
||||
```
|
||||
unroll 80 {
|
||||
cx16.VERA_DATA0 = 255
|
||||
}
|
||||
```
|
||||
Prog8 supports `if`/`else`, `when`, `for`, `while`, `do`/`until`, `repeat`,
|
||||
`unroll`, `break`, `continue`, `goto`, and labels. `unroll` duplicates a
|
||||
constant-count body at compile time and does not support `break` or `continue`.
|
||||
`repeat` is usually more efficient than `for` when no loop variable is needed.
|
||||
Replace a `for` loop with a `repeat` loop when the loop variable is not used in the loop body.
|
||||
Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
|
||||
|
||||
## Subroutines & Return Values
|
||||
- Symbols public by default; use `private` to restrict to block
|
||||
- `inline` keyword for subroutines to suggest inlining
|
||||
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
|
||||
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
|
||||
- Nested subroutines access parent scope variables directly
|
||||
## Subroutines and Assembly
|
||||
|
||||
## Assembly Subroutines (asmsub)
|
||||
For kernel (ROM) routines or low-level assembly that gets arguments via registers.
|
||||
- Parameter passing via registers: `@A` (accu), `@X`, `@Y`, `@AX`/`@AY` (16-bit), `@R0`-`@R15` (virtual regs), `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use)
|
||||
- Clobbers: `clobbers (A, X, Y)` — list all modified registers
|
||||
- Parameter names are documentation only — use registers in assembly, not parameter names
|
||||
- Create symbolic aliases at assembly start: `x1 = cx16.r0`, `x1_lo = cx16.r0L`
|
||||
- Example:
|
||||
```prog8
|
||||
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
x1 = cx16.r0
|
||||
x2 = cx16.r1
|
||||
lda x1
|
||||
}}
|
||||
}
|
||||
```
|
||||
- Symbol prefixes in assembly: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- Fully qualified: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
|
||||
- Within a `.proc`, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
|
||||
- Split word arrays: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
|
||||
- CX16 target only: use 65C02 instructions (STZ etc). Others: 6502 only
|
||||
- Assembly syntax: 64tass assembler. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- Instructions like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`, not just `rol`
|
||||
- Anonymous labels: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
- Subroutines can return zero, one, or multiple values:
|
||||
`a, b = routine()` or `void routine()`.
|
||||
- On M68K, ordinary calls preserve the caller's active frame. A `goto` or branch
|
||||
that leaves the current subroutine must tear that frame down before transferring
|
||||
control. The built-in `call(address)` is a zero-argument indirect call; it does
|
||||
not marshal stack arguments.
|
||||
- Avoid `private` unless requested. Prog8 symbols are public by default.
|
||||
- `asmsub` bodies contain only one `%asm {{ ... }}` node. Parameters are type
|
||||
checked and documented, but assembly must use the mapped registers. Declare
|
||||
every modified hardware register in `clobbers`.
|
||||
- On M68K targets never clobber `A5`, the stack frame pointer. On the Amiga
|
||||
targets never clobber `A6` either, it holds the AmigaOS library base.
|
||||
- `extsub` maps a signature to a fixed external address and has no body. Use
|
||||
it for ROM, kernel, drivers, or binary-library routines.
|
||||
- For inline assembly, load `asm6502-coder` for 64tass syntax, target-specific
|
||||
instructions, zero-page rules, and symbol references. Load `m68k-coder` for
|
||||
M68000 assembly.
|
||||
|
||||
## Standard Library
|
||||
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
|
||||
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
|
||||
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
|
||||
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
|
||||
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
|
||||
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
|
||||
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
|
||||
- String functions return useful lengths — capture them: `len = strings.copy(dest, src)`, `len = strings.append(buf, text)`, `len = strings.upper(mystr)`
|
||||
## Interrupts and Hardware
|
||||
|
||||
## Syntax & Formatting
|
||||
- Hex: `$FF` (not `0xFF`); Binary: `%1010` (not `0b1010`). Underscores for readability: `25_000_000`
|
||||
- 4-digit hex `$0000` = uword. No type suffixes (no `0L`). Cast: `expr as type`
|
||||
- Augmented assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
||||
- **`;` starts a comment to end of line** — NOT a statement separator. One statement per line
|
||||
- **No `elif`**: use nested `else { if ... }`
|
||||
- Type casting: `expression as type` (e.g., `bytevar as word`). `as` has very low precedence (lower than arithmetic)
|
||||
- **No automatic type widening**: `byte*byte=byte` (overflow possible!). Cast explicitly
|
||||
- **No block scope**: `for`/`if` don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level
|
||||
- **No bare `{ }` blocks** like C/Java
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
|
||||
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
|
||||
- **No block scope** — variables in `if`/`for`/`repeat` blocks are hoisted to subroutine level
|
||||
- **No reassigning strings** after declaration — use `strings.copy()`/`strings.ncopy()`
|
||||
- **Pre-allocate string buffers** — `""` allocates nothing
|
||||
- Keep IRQ handlers short. Set a flag and do substantial work in the main loop.
|
||||
- Virtual registers `R0-R15` are not preserved across IRQ handlers. Avoid them
|
||||
or save and restore them with the library routines.
|
||||
- CX16 IRQ handlers that touch VERA registers must save and restore VERA
|
||||
context.
|
||||
- Use only symbolic scratch names such as `P8ZP_SCRATCH_W1` and
|
||||
`cx16.r0`; never hardcode zeropage addresses.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use the `virtual` target for behavioral tests when possible:
|
||||
`prog8c -target virtual -emu program.p8`.
|
||||
- Use `-check` for syntax and semantic checks without output generation.
|
||||
- Use `-noopt` to determine whether a failure is optimizer-related.
|
||||
- For IR execution, use `-vmtrace` when control flow needs inspection.
|
||||
@@ -13,5 +13,8 @@ dist/
|
||||
build/
|
||||
out/
|
||||
output/
|
||||
gradle/
|
||||
Libs/
|
||||
.gradle/
|
||||
.kotlin/
|
||||
.git/
|
||||
@@ -10,32 +10,45 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Build and test with Gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Compile
|
||||
run: ./gradlew compileKotlin compileTestKotlin --console=plain
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test --continue --max-workers=3 -PtestConcurrency=2 -PtestMaxHeap=8g --console=plain
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
./gradlew shadowJar --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: |
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/build/test-results/test/
|
||||
+8
-2
@@ -5,6 +5,7 @@
|
||||
.idea/shelf/
|
||||
.junie/
|
||||
.junie/memory/
|
||||
.serena
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
@@ -14,8 +15,11 @@ out-old/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
*.elf
|
||||
*.o
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
@@ -38,8 +42,10 @@ parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
AsyncIO/
|
||||
AsyncIO.info
|
||||
m68k-amigaos-ahi/
|
||||
Libs/
|
||||
Generated
+12
-2
@@ -2,6 +2,16 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="prog8.codeGenExperimental" target="17" />
|
||||
<module name="prog8.codeGenExperimental.main" target="17" />
|
||||
<module name="prog8.codeGenExperimental.test" target="17" />
|
||||
<module name="prog8.codeGenM68k" target="17" />
|
||||
<module name="prog8.codeGenM68k.main" target="17" />
|
||||
<module name="prog8.codeGenM68k.test" target="17" />
|
||||
<module name="prog8.codeGenNew6502" target="17" />
|
||||
<module name="prog8.codeGenNew6502.main" target="17" />
|
||||
<module name="prog8.codeGenNew6502.test" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/codeCore" />
|
||||
<option value="$PROJECT_DIR$/codeGenCpu6502" />
|
||||
<option value="$PROJECT_DIR$/codeGenIntermediate" />
|
||||
<option value="$PROJECT_DIR$/codeGenM68k" />
|
||||
<option value="$PROJECT_DIR$/codeGenNew6502" />
|
||||
<option value="$PROJECT_DIR$/codeOptimizers" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/compilerAst" />
|
||||
<option value="$PROJECT_DIR$/intermediate" />
|
||||
<option value="$PROJECT_DIR$/languageServer" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
<option value="$PROJECT_DIR$/simpleAst" />
|
||||
<option value="$PROJECT_DIR$/virtualmachine" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myGradleHome" value="/usr/share/java/gradle" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+4
-3
@@ -7,13 +7,14 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.3" />
|
||||
<option name="languageVersion" value="2.3" />
|
||||
<option name="apiVersion" value="2.4" />
|
||||
<option name="languageVersion" value="2.4" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.3.20" />
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-13
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-14
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.irmen.ksim65" type="repository">
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.0/ksim65-v2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.framework.datatest" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
@@ -20,6 +20,7 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
Generated
-14
@@ -3,22 +3,8 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/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" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"context": {
|
||||
"fileName": "AGENTS.md"
|
||||
},
|
||||
"$version": 3,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(prog8c *)",
|
||||
"Bash(gradle *)",
|
||||
"Bash(mkdir *)",
|
||||
"Bash(ls *)",
|
||||
"Bash(cat *)",
|
||||
"Bash(find *)",
|
||||
"Bash(sed *)",
|
||||
"Bash(grep *)",
|
||||
"Bash(xargs *)",
|
||||
"Bash(wc *)",
|
||||
"Bash(diff *)",
|
||||
"Bash(paste *)",
|
||||
"Bash(echo *)",
|
||||
"Bash(cp *)",
|
||||
"Bash(time)",
|
||||
"Bash(kotlinc *)",
|
||||
"Bash(python3 *)",
|
||||
"Bash(jar *)",
|
||||
"Bash(od *)",
|
||||
"Bash(xxd *)",
|
||||
"Bash(gcc *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,28 +13,35 @@
|
||||
|
||||
## CHANGE / EDIT MODE
|
||||
|
||||
- Never implement features yourself when possible - use sub-agents!
|
||||
- Use a subagent for larger chunks of work
|
||||
- Identify changes from the plan that can be implemented in parallel, and use sub-agents to implement the features efficiently
|
||||
- When using sub-agents to implement features, act as a coordinator only
|
||||
- Use the best model for the task - premium models for complex tasks (like coding) and mid-tier models for simpler tasks, like documentation
|
||||
- After completing features (large or small), always run commands like lint, type check and next build to check code quality
|
||||
- After completing features (large or small), run the relevant quality commands (lint, type check, build) to check code quality.
|
||||
Only compiler/standard-library changes require a build: that means Kotlin sources (`.kt`), the grammar (`.g4`), or embedded
|
||||
standard library files (`compiler/res/prog8lib/**/*.p8` and `**/*.asm`). For changes limited to `examples/**`, `docs/**`, or
|
||||
other non-compiler files, verify with the target compiler directly instead (for example `prog8c -target <target> <file>`), and
|
||||
skip the build entirely
|
||||
(exception: if the change is only whitespace or code comments, a build or test run is not necessary)
|
||||
- `gradle build` already runs the full test suite; don't run a separate full `gradle test` before it, to avoid lengthy double test runs (targeted `--tests` filters to find failing tests are fine)
|
||||
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
|
||||
- The IR code is used by machine-specific code generator backends, including 6502 and M68K.
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension – these are *not* LUA or PICO-8 source files in this case!
|
||||
- Prog8 source files are a "module" that can contain one or more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
|
||||
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
|
||||
- Kotlin version 2.3 is used for the compiler implementation.
|
||||
- Kotlin version 2.4 is used for the compiler implementation.
|
||||
- Java 17 is used as Java runtime version.
|
||||
- ANTLR4 version 4.13 is used for the parser implementation.
|
||||
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
|
||||
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
|
||||
- **Codebase knowledge graph**: For a structured overview of modules, their dependencies, key entities, the end-to-end compilation pipeline, the standard library layout, and the test structure, consult `docs/CODEBASE-KNOWLEDGE-GRAPH.md`. Read it when you need to understand how the compiler fits together before diving into specific files.
|
||||
|
||||
## Compilation Flow (High-Level)
|
||||
```
|
||||
@@ -53,7 +60,9 @@ Source → parseMainModule() → processAst() → optimizeAst() → postprocessA
|
||||
|
||||
### Target Differences
|
||||
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
|
||||
- When writing or understanding assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
|
||||
- When writing or understanding 6502/65C02 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
|
||||
- When writing or understanding M68K assembly code (amiga500, amiga1200, qemu68k targets), load the `m68k-coder` skill for vasm mot syntax and conventions.
|
||||
- M68K ordinary subroutine locals and parameters use `A5`-based stack frames. Normal calls pass word-packed argument slots on the stack (2-byte slots for byte/word args, 4-byte for wide args, byte values at the slot base), and the caller cleans up; see `docs/source/technical.rst` for the complete convention and frame eligibility rules.
|
||||
|
||||
|
||||
## DEBUGGING TIP: Use `-noopt` to isolate problems
|
||||
@@ -125,7 +134,25 @@ Steps when encountering a compiler crash:
|
||||
- **NO EMOJI in user documentation**. Do not use emoji or decorative unicode symbols in documentation files. Functional unicode symbols are acceptable when they serve a clear purpose (e.g., → for arrows, ± for plus-minus, × for multiplication, ° for degrees). Avoid decorative emoji like ❌ ✅ ⚠️ 🎉 etc.
|
||||
|
||||
### Code Style Guidelines
|
||||
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
|
||||
**Code comments**: Comments are the exception, not the norm. A comment earns its place only by recording
|
||||
something the code itself cannot say: the reason for a non-obvious choice, or a constraint that
|
||||
a well-meaning future edit would otherwise violate.
|
||||
|
||||
- Inline comment inside a function body: 1 line, never more.
|
||||
- KDoc on a declaration: 3 lines, unless a genuine non-obvious contract cannot be stated shorter.
|
||||
- Never restate the identifier, the type, or what the following line does.
|
||||
- Prefer a good name or a well-named function over a comment that explains it.
|
||||
|
||||
Before keeping any comment, delete it mentally and ask what the reader loses. If nothing, it goes.
|
||||
|
||||
Keep existing comments as they are; do not expand, reword, or reformat them while editing
|
||||
nearby lines - unless that would cause inconsistency.
|
||||
|
||||
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
|
||||
```kotlin
|
||||
val bill = $$"The value is $3.99"
|
||||
```
|
||||
|
||||
|
||||
## Prog8 language information
|
||||
|
||||
@@ -153,18 +180,6 @@ When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir`
|
||||
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
## Junie Agent Commitments
|
||||
These instructions apply specifically to the Junie agent:
|
||||
|
||||
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
|
||||
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
|
||||
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
|
||||
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
|
||||
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
|
||||
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
|
||||
|
||||
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
|
||||
|
||||
# Dev environment tips
|
||||
|
||||
## Development Workflows
|
||||
@@ -202,9 +217,12 @@ These instructions apply specifically to the Junie agent:
|
||||
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
|
||||
|
||||
### When to run full build with tests
|
||||
- **Before committing changes**: Always run `gradle build` to ensure all tests pass
|
||||
- **Before final verification**: Run `gradle build` to ensure all tests pass - but only if the change actually touched the
|
||||
compiler or standard library (`.kt`, `.g4`, or `compiler/res/prog8lib/**`). For `examples/**` or `docs/**`-only changes,
|
||||
verify with `prog8c` directly instead and skip the build
|
||||
- **After major refactoring**: Run `gradle build` to catch regressions
|
||||
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
|
||||
- **When working on a standalone code gen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification if the change touched compiler sources.
|
||||
|
||||
### Build command summary
|
||||
|
||||
@@ -221,12 +239,15 @@ These instructions apply specifically to the Junie agent:
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- **the `-check` switch performs a quick syntax/semantic check only; it will NOT produce any output files (no .prg, .asm, etc.)**. Use it only for fast error checking during development.
|
||||
- **the `-noopt` switch DISABLES all optimizations** - useful for debugging to determine if a problem is caused by the optimizer. **Optimizations are ENABLED by default** (no flag needed).
|
||||
- **prog8c uses single-dash command line options** (e.g., `-target`, `-noopt`, `-check`), NOT double-dash (`--target` is invalid).
|
||||
- **prog8c options accept single-dash or double-dash** (e.g., `-target` and `--target` are equivalent).
|
||||
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
|
||||
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the current working directory**.
|
||||
- **the `-o <file>` switch names the exact final program artifact** (also spelled `--output`); all other output files go in the same directory with the same stem. Cannot be combined with `-out` or with multiple source files.
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
|
||||
|
||||
### Compilation Output Files
|
||||
Names follow the source file name, or the stem of the `-o` file when that option is used.
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
@@ -238,19 +259,33 @@ These instructions apply specifically to the Junie agent:
|
||||
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
|
||||
### Library Tools
|
||||
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
|
||||
- in prog8 code being tested, use '@shared' variables to avoid const-folding
|
||||
|
||||
### Automated Tests (gradle)
|
||||
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
|
||||
- `gradle build --console=plain` - Full build including all tests (about 50s)
|
||||
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
|
||||
- `gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
|
||||
- `ionice -c 3 nice gradle build --console=plain` - Full build including all tests (about 50s)
|
||||
- `ionice -c 3 nice gradle :compiler:test --console=plain` - Run only compiler tests (faster)
|
||||
- `ionice -c 3 nice gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
|
||||
|
||||
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
|
||||
|
||||
On Unix-like systems, prefix **every** gradle invocation with `nice` to lower its CPU priority,
|
||||
because gradle is CPU-heavy. This applies to all gradle commands (build, compile, test, install), not
|
||||
just `gradle build`. Use the same prefix for the quick compile checks and install commands in
|
||||
"Building and Installing the Compiler" above.
|
||||
|
||||
On **Linux** additionally put `ionice -c 3` in front (idle-class I/O scheduling), so the build doesn't
|
||||
compete for disk with whatever else is running: `ionice -c 3 nice gradle ...`. `ionice` is
|
||||
Linux-only (util-linux); on macOS/BSD just use `nice`.
|
||||
|
||||
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
|
||||
|
||||
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
|
||||
@@ -281,13 +316,47 @@ gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
- Unit tests use KoTest (FunSpec style)
|
||||
- Tests are in the "compiler" module's `test` directory (and some other modules)
|
||||
- Test config is in root `build.gradle.kts`; tests run in parallel
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit`
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit` - the latter ONLY when NOT writing for the amiga500 or qemu68k targets
|
||||
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
|
||||
|
||||
### TestCompilerOnExamples expected sizes
|
||||
`TestCompilerOnExamples` asserts exact output sizes per example. A code-generator change shifts them,
|
||||
so expect a handful of `expected:<N> but was:<M>` failures with no stack trace pointing at the cause.
|
||||
Attribute each one before editing: rebuild with the change reverted and compare the two generated
|
||||
`.asm` files, so the size delta is demonstrably yours and not a side effect. Then update the expected
|
||||
size. A delta of a few bytes from removed `ldy`/`lda` instructions is normal; a large or erratic delta
|
||||
means something else changed and needs investigating.
|
||||
|
||||
### Compiler Unit Test Snippets
|
||||
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
|
||||
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
|
||||
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
|
||||
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
|
||||
- **Set `optimize=false`** by default for these snippets
|
||||
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
|
||||
- **Do NOT** write compiler tests that depend on the `vasm` assembler: it is not always installed
|
||||
(CI only installs `64tass`). For m68k codegen tests (`amiga500`, `qemu68k` targets) pass
|
||||
`writeAssembly = true, assemble = false` to the `compileText`/`compileFile` helpers. This still
|
||||
generates the codegen AST to assert on but skips assembling, so no vasm is needed. If a test's
|
||||
assertions cannot be made from the AST alone, drop the test.
|
||||
|
||||
### Testing Assembly Output
|
||||
When testing generated assembly in Kotlin tests:
|
||||
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
|
||||
- **Instead, parse into lines and check specific properties:**
|
||||
```kotlin
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
lines.any { it == "lda #42" } shouldBe true
|
||||
lines.count { it.startsWith("jsr") } shouldBe 0
|
||||
```
|
||||
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
|
||||
|
||||
### Manual Verification & Emulators
|
||||
|
||||
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
|
||||
|
||||
For the amiga targets, `-emu` launches FS-UAE (GUI) while `-emu2` launches Vamos (headless CLI). For automated/non-interactive testing, prefer `-emu2` (vamos) to avoid opening a GUI window.
|
||||
|
||||
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
|
||||
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
|
||||
```prog8
|
||||
|
||||
@@ -12,9 +12,9 @@ endif
|
||||
|
||||
all:
|
||||
$(GRADLE) installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
$(GRADLE) build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
@@ -4,21 +4,24 @@ PayPal: []
|
||||
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
============================================================================
|
||||
Prog8 - Structured Programming Language for 6502/65c02 (8-bit) and 68000 (32-bit) microprocessors
|
||||
=================================================================================================
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||
as used in many home computers from that era. It is a medium to low level programming language,
|
||||
as used in many home computers from that era. It also supports the 32-bit Motorola 68000 CPU, for which the
|
||||
Commodore Amiga is the intended machine. It is a medium to low level programming language,
|
||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||
|
||||
**Want to buy me a coffee or a pizza perhaps?**
|
||||
|
||||
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
|
||||
This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can.
|
||||
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
|
||||
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
|
||||
|
||||
[](https://jb.gg/OpenSource)
|
||||
I am using JetBrains products to develop the Prog8 compiler.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -63,6 +66,7 @@ What does Prog8 provide?
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
@@ -91,7 +95,7 @@ What does Prog8 provide?
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- can automatically run the program in the Vice emulator after successful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
@@ -101,17 +105,22 @@ What does Prog8 provide?
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "pet32": Commodore PET (limited support)
|
||||
- "amiga500": Commodore Amiga 500 (16/32-bit 68000 CPU; experimental)
|
||||
- "amiga1200": Commodore Amiga 1200 (32-bit 68020 CPU with optional FPU; experimental)
|
||||
- "qemu68k": Qemu M68k simulator (experimental)
|
||||
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
The m68k targets (amiga500, amiga1200, qemu68k) and the optional `-newcodegen` 6502 code generator are experimental and not yet suitable for production code.
|
||||
|
||||
|
||||
|
||||
Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
|
||||
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
|
||||
+13
-12
@@ -41,7 +41,7 @@ rotate3d {
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 600 + rz/64
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
@@ -51,7 +51,7 @@ rotate3d {
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 600 + rz/64
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
@@ -84,18 +84,19 @@ matrix_math {
|
||||
word wcosc = math.cos8(az)
|
||||
word wsinc = math.sin8(az)
|
||||
|
||||
word wcosa_sinb = wcosa*wsinb / 128
|
||||
word wsina_sinb = wsina*wsinb / 128
|
||||
; instead of (slow) /128, we simply shift 7 bits (and take the roundoff error)
|
||||
word wcosa_sinb = wcosa*wsinb >> 7
|
||||
word wsina_sinb = wsina*wsinb >> 7
|
||||
|
||||
word Axx = wcosa*wcosb / 128
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
|
||||
word Ayx = wsina*wcosb / 128
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
|
||||
word Axx = wcosa*wcosb >> 7
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) >> 7
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) >> 7
|
||||
word Ayx = wsina*wcosb >> 7
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) >> 7
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) >> 7
|
||||
word Azx = -wsinb
|
||||
word Azy = wcosb*wsinc / 128
|
||||
word Azz = wcosb*wcosc / 128
|
||||
word Azy = wcosb*wsinc >> 7
|
||||
word Azz = wcosb*wcosc >> 7
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
|
||||
@@ -220,10 +220,10 @@ btree {
|
||||
|
||||
arena {
|
||||
; extremely trivial arena allocator (that never frees)
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
uword next = buffer
|
||||
pointer buffer = memory("arena", 2000, 0)
|
||||
pointer next = buffer
|
||||
|
||||
sub alloc(ubyte size) -> uword {
|
||||
sub alloc(ubyte size) -> pointer {
|
||||
defer next += size
|
||||
return next
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ life {
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
repeat HEIGHT {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
;
|
||||
; As the X16 computer is a more or less fixed system, it's not very useful
|
||||
; to benchmark the computer itself with.
|
||||
;
|
||||
; The individual scores are iteration counts, and they differ by more than an
|
||||
; order of magnitude between benchmarks (maze does ~3000, game of life ~180).
|
||||
; Summing the raw counts therefore let one or two benchmarks dominate the total
|
||||
; and hide regressions in all the others, so the total is a weighted sum - see
|
||||
; benchmark_weight below.
|
||||
|
||||
|
||||
%import textio
|
||||
@@ -26,7 +32,15 @@ main {
|
||||
|
||||
str[20] benchmark_names
|
||||
uword[20] benchmark_score
|
||||
str version = "12.1"
|
||||
; Weight per benchmark, in the same order as the benchmarks are run below. The total is the
|
||||
; weighted sum of the raw iteration counts.
|
||||
; The raw counts differ by more than an order of magnitude (maze ~3064, game of life ~180), so
|
||||
; in an unweighted sum the two heaviest - maze solver and text-elite - were 57% of the total
|
||||
; while game of life was 2%. A regression in a small benchmark was therefore nearly invisible.
|
||||
; Their weights are scaled down so that every benchmark contributes roughly comparably; the
|
||||
; other eight are left at 256 (i.e. unweighted).
|
||||
uword[10] benchmark_weight = [ 64, 256, 256, 256, 256, 256, 256, 64, 256, 256 ]
|
||||
str version = "14.0"
|
||||
|
||||
|
||||
sub start() {
|
||||
@@ -96,11 +110,14 @@ main {
|
||||
txt.print(")\n\n")
|
||||
|
||||
do {
|
||||
; Weighted contribution. Done in long arithmetic on purpose: score*weight overflows a
|
||||
; uword (3064*256 is 784384, which wraps to 63488 and would corrupt the total).
|
||||
uword weighted = ((benchmark_score[benchmark_number] as long) * benchmark_weight[benchmark_number] / 256) as uword
|
||||
txt.spc()
|
||||
txt.print_uw(benchmark_score[benchmark_number])
|
||||
txt.print_uw(weighted)
|
||||
txt.column(6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
total_score += benchmark_score[benchmark_number]
|
||||
total_score += weighted
|
||||
txt.nl()
|
||||
benchmark_number++
|
||||
} until benchmark_names[benchmark_number]==0
|
||||
|
||||
+12
-6
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.20"
|
||||
kotlin("jvm") version "2.4.21"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -52,8 +52,8 @@ subprojects {
|
||||
// Common test dependencies via Kotest BOM (Bill of Materials)
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
|
||||
// implementation("com.github.irmen:ksim65:v2.0")
|
||||
testImplementation(platform("io.kotest:kotest-bom:6.2.4"))
|
||||
// implementation("org.codeberg.irmen:ksim65:v2.2")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
@@ -72,16 +72,22 @@ subprojects {
|
||||
// Enable JUnit 5 (required for Kotest)
|
||||
useJUnitPlatform()
|
||||
|
||||
// Forked test JVMs each reserve up to -PtestMaxHeap, so keep the fork count
|
||||
// bounded even on many-core machines to avoid swapping during parallel builds.
|
||||
val testConcurrency = (project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
|
||||
?: (Runtime.getRuntime().availableProcessors() / 2)).coerceIn(1, 4)
|
||||
project.findProperty("testMaxHeap")?.toString()?.let { maxHeapSize = it }
|
||||
|
||||
// Enable concurrent test execution for Kotest
|
||||
// Set parallelism to number of CPU cores
|
||||
jvmArgs("-Dkotest.framework.parallelism=${Runtime.getRuntime().availableProcessors()}")
|
||||
// Allow CI to override parallelism for memory-constrained runners
|
||||
jvmArgs("-Dkotest.framework.parallelism=$testConcurrency")
|
||||
|
||||
// Disable Kotest autoscan warning
|
||||
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
|
||||
|
||||
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
|
||||
// Use 50% of available processors to avoid over-subscription
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
maxParallelForks = testConcurrency
|
||||
|
||||
// Allow filtering tests via -PtestFilter="*TestName*" from command line
|
||||
// Example: gradle test -PtestFilter="*TestLookup*"
|
||||
|
||||
@@ -8,5 +8,4 @@ dependencies {
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -40,12 +40,10 @@ object SymbolNames {
|
||||
* See https://github.com/irmen/prog8/issues/198
|
||||
*/
|
||||
fun stripPrefixes(scopedName: String): String {
|
||||
return scopedName.split('.')
|
||||
.map { part ->
|
||||
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
|
||||
part.drop(4)
|
||||
else part
|
||||
}
|
||||
.joinToString(".")
|
||||
return scopedName.split('.').joinToString(".") { part ->
|
||||
if (part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
|
||||
part.drop(4)
|
||||
else part
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
-40
@@ -1,38 +1,29 @@
|
||||
package prog8.codegen.cpu6502
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.OutputType
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.PETTarget
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal class AssemblyProgram(
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
class AssemblyProgram6502(
|
||||
override val name: String,
|
||||
private val options: CompilationOptions,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
||||
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
|
||||
private val listFile = outputDir.resolve("$name.list")
|
||||
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
|
||||
val assemblerCommand: List<String>
|
||||
val outs = OutputFiles.of(options, name)
|
||||
|
||||
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
|
||||
// keep additionalAssemblerOptions BEFORE --output: a target config may contain its own --output
|
||||
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
|
||||
command.addAll(options.compTarget.additionalAssemblerOptions)
|
||||
|
||||
@@ -45,7 +36,7 @@ internal class AssemblyProgram(
|
||||
// CBM machines .prg generation.
|
||||
|
||||
val command = mutableListOf("64tass", "--cbm-prg", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
@@ -56,10 +47,10 @@ internal class AssemblyProgram(
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile) {
|
||||
command.add("--list=$listFile")
|
||||
command.add("--list=${outs.list()}")
|
||||
}
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating prg for target ${compTarget.name}.")
|
||||
}
|
||||
@@ -67,7 +58,7 @@ internal class AssemblyProgram(
|
||||
// Atari800XL .xex generation.
|
||||
|
||||
val command = mutableListOf("64tass", "--atari-xex", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
@@ -78,16 +69,16 @@ internal class AssemblyProgram(
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=$listFile")
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating xex for target ${compTarget.name}.")
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
// Neo6502/headerless raw program generation.
|
||||
val command = mutableListOf("64tass", "--nostart", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
@@ -98,9 +89,9 @@ internal class AssemblyProgram(
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=$listFile")
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating raw binary for target ${compTarget.name}.")
|
||||
}
|
||||
@@ -108,7 +99,7 @@ internal class AssemblyProgram(
|
||||
// CBM machines library (.bin) generation (with or without 2 byte load address header depending on the compilation target machine)
|
||||
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile")
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
@@ -119,7 +110,7 @@ internal class AssemblyProgram(
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=$listFile")
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
if(compTarget.name in listOf(C64Target.NAME, C128Target.NAME, PETTarget.NAME)) {
|
||||
if(!options.quiet)
|
||||
@@ -131,26 +122,45 @@ internal class AssemblyProgram(
|
||||
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
|
||||
}
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
}
|
||||
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(assemblerCommand)
|
||||
if(!options.quiet)
|
||||
proc.inheritIO()
|
||||
val process = proc.start()
|
||||
.redirectErrorStream(true)
|
||||
|
||||
if (options.quiet) {
|
||||
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
}
|
||||
|
||||
val process = try {
|
||||
proc.start()
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: assembler failed to start: ${e.message}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!options.quiet) {
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.forEachLine {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = process.waitFor()
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
removeGeneratedLabelsFromMonlist(outs)
|
||||
generateBreakpointList(outs)
|
||||
}
|
||||
return result==0
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
private fun removeGeneratedLabelsFromMonlist(outs: OutputFiles) {
|
||||
val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""")
|
||||
val lines = viceMonListFile.toFile().readLines()
|
||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||
val lines = outs.viceMonList().toFile().readLines()
|
||||
outs.viceMonList().toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
if(pattern.matchEntire(line)==null)
|
||||
it.write(line+"\n")
|
||||
@@ -158,11 +168,11 @@ internal class AssemblyProgram(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateBreakpointList() {
|
||||
private fun generateBreakpointList(outs: OutputFiles) {
|
||||
// builds list of breakpoints, appends to monitor list file
|
||||
val breakpoints = mutableListOf<String>()
|
||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
||||
for (line in viceMonListFile.toFile().readLines()) {
|
||||
for (line in outs.viceMonList().toFile().readLines()) {
|
||||
val match = pattern.matchEntire(line)
|
||||
if (match != null)
|
||||
breakpoints.add("break $" + match.groupValues[1])
|
||||
@@ -171,6 +181,7 @@ internal class AssemblyProgram(
|
||||
breakpoints.add(0, "; breakpoint list now follows")
|
||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||
breakpoints.add(2, "del")
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
outs.viceMonList().toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
val irInstructionCount: Int
|
||||
val irChunkCount: Int
|
||||
val irRegisterCount: Int
|
||||
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returnTypes: Array<BaseDataType>,
|
||||
vararg val parameters: FParam) {
|
||||
|
||||
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
fun callConventionFor6502(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
val returns: ReturnConvention
|
||||
if(returnTypes.isEmpty())
|
||||
returns = ReturnConvention(null, null)
|
||||
@@ -41,7 +41,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returnType = returnTypes[0]
|
||||
returns = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
@@ -49,7 +49,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
@@ -109,9 +109,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"abs" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"abs" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
@@ -120,7 +120,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
@@ -140,50 +140,55 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, emptyArray(), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD)),
|
||||
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD)),
|
||||
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
|
||||
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD)),
|
||||
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD)),
|
||||
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
|
||||
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekp" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokep" to FSignature(false, emptyArray(), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.POINTER)),
|
||||
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, emptyArray()),
|
||||
"rrestore" to FSignature(false, emptyArray()),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.POINTER)),
|
||||
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
|
||||
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
|
||||
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
|
||||
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
|
||||
"pushp" to FSignature(false, emptyArray(), FParam("value", BaseDataType.POINTER)),
|
||||
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
|
||||
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
|
||||
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
|
||||
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
|
||||
"popp" to FSignature(false, arrayOf(BaseDataType.POINTER)),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
class CompilationOptions(val output: OutputType,
|
||||
val launcher: CbmPrgLauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
@@ -17,6 +18,7 @@ class CompilationOptions(val output: OutputType,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
var varsAddress: UInt? = null,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var warnImplicitTypeCast: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
@@ -25,17 +27,17 @@ class CompilationOptions(val output: OutputType,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var newCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
var slabsHighBank: Int? = null,
|
||||
var slabsGolden: Boolean = false,
|
||||
var breakpointCpuInstruction: String? = null,
|
||||
var ignoreFootguns: Boolean = false,
|
||||
var outputDir: Path = Path(""),
|
||||
var quiet: Boolean = false,
|
||||
var profilingInstrumentation: Boolean = false,
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
var symbolDefs: Map<String, String> = emptyMap(),
|
||||
var privateSymbols: Boolean = false,
|
||||
var outputFile: Path? = null
|
||||
) {
|
||||
init {
|
||||
compTarget.initializeMemoryAreas(this)
|
||||
@@ -58,6 +60,7 @@ class CompilationOptions(val output: OutputType,
|
||||
private var compilerVersion: String = "unknown"
|
||||
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
|
||||
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
|
||||
private var varsAddress: UInt? = null
|
||||
private var warnSymbolShadowing: Boolean = false
|
||||
private var warnImplicitTypeCast: Boolean = false
|
||||
private var optimize: Boolean = false
|
||||
@@ -66,17 +69,16 @@ class CompilationOptions(val output: OutputType,
|
||||
private var includeSourcelines: Boolean = false
|
||||
private var dumpVariables: Boolean = false
|
||||
private var dumpSymbols: Boolean = false
|
||||
private var experimentalCodegen: Boolean = false
|
||||
private var varsHighBank: Int? = null
|
||||
private var varsGolden: Boolean = false
|
||||
private var slabsHighBank: Int? = null
|
||||
private var slabsGolden: Boolean = false
|
||||
private var breakpointCpuInstruction: String? = null
|
||||
private var ignoreFootguns: Boolean = false
|
||||
private var outputDir: Path = Path("")
|
||||
private var quiet: Boolean = false
|
||||
private var profilingInstrumentation: Boolean = false
|
||||
private var newCodegen: Boolean = false
|
||||
private var symbolDefs: Map<String, String> = emptyMap()
|
||||
private var outputFile: Path? = null
|
||||
|
||||
fun output(output: OutputType) = apply { this.output = output }
|
||||
fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher }
|
||||
@@ -89,6 +91,7 @@ class CompilationOptions(val output: OutputType,
|
||||
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
|
||||
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
|
||||
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
|
||||
fun varsAddress(varsAddress: UInt?) = apply { this.varsAddress = varsAddress }
|
||||
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
|
||||
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
|
||||
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
|
||||
@@ -97,26 +100,25 @@ class CompilationOptions(val output: OutputType,
|
||||
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
|
||||
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
|
||||
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
|
||||
fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
|
||||
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
|
||||
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
|
||||
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
|
||||
fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden }
|
||||
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
|
||||
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
|
||||
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
|
||||
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
|
||||
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
|
||||
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
|
||||
fun newCodegen(newCodegen: Boolean) = apply { this.newCodegen = newCodegen }
|
||||
fun outputFile(outputFile: Path?) = apply { this.outputFile = outputFile }
|
||||
|
||||
fun build(): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
|
||||
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
|
||||
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
loadAddress, memtopAddress, varsAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, newCodegen, varsHighBank, varsGolden,
|
||||
breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
)
|
||||
).apply { this.outputFile = this@Builder.outputFile }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,3 +98,24 @@ fun String.unescape(): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
// === Symbol name prefixing ===
|
||||
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
|
||||
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
|
||||
// Scoped names (containing dots) have each part prefixed according to its role.
|
||||
|
||||
fun prefixScopedName(name: String, type: Char): String {
|
||||
val generatedPrefix = "p8_label_gen_"
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(generatedPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.*
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -20,7 +22,6 @@ enum class BaseDataType {
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
@@ -30,22 +31,28 @@ enum class BaseDataType {
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
this == STR && (other == UWORD || other == LONG) || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other != FLOAT
|
||||
this == STR -> other != FLOAT
|
||||
this.isArray -> other.isByteOrBool
|
||||
this == STR -> other.isByteOrBool
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType) =
|
||||
fun equalsSize(other: BaseDataType): Boolean = equalsSize(other, null)
|
||||
fun equalsSize(other: BaseDataType, memsizer: IMemSizer?): Boolean =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || other.isPointer
|
||||
this.isPointer -> other.isWord
|
||||
this == STR && other== UWORD || this== UWORD && other== STR -> true
|
||||
this.isWord -> other.isWord || (other.isPointer && (memsizer == null || memsizer.POINTER_MEM_SIZE == 2u))
|
||||
this.isLong -> other.isLong || (other.isPointer && memsizer?.POINTER_MEM_SIZE != null && memsizer.POINTER_MEM_SIZE != 2u)
|
||||
this.isPointer -> when(memsizer?.POINTER_MEM_SIZE) {
|
||||
null -> other.isWord || other.isPointer
|
||||
2u -> other.isWord || other.isPointer
|
||||
else -> other.isLong || other.isPointer
|
||||
}
|
||||
this == STR && (other == UWORD || other == LONG) || (this == UWORD || this == LONG) && other == STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
@@ -68,17 +75,27 @@ val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.maxUnsignedValue: Double
|
||||
get() = when (this) {
|
||||
BaseDataType.UBYTE -> 255.0
|
||||
BaseDataType.BYTE -> 127.0
|
||||
BaseDataType.UWORD -> 65535.0
|
||||
BaseDataType.WORD -> 32767.0
|
||||
BaseDataType.LONG -> 2147483647.0
|
||||
else -> Double.MAX_VALUE
|
||||
}
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(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 setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isUnsigned get() = !this.isSigned
|
||||
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
|
||||
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
@@ -109,18 +126,21 @@ interface ISubType {
|
||||
class DataType private constructor(
|
||||
val base: BaseDataType,
|
||||
val sub: BaseDataType?,
|
||||
val pointeeSub: BaseDataType?,
|
||||
var subType: ISubType?,
|
||||
var subTypeFromAntlr: List<String>? = null
|
||||
) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isPointerArray -> {
|
||||
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
|
||||
base.isArray && sub == BaseDataType.POINTER -> {
|
||||
require(pointeeSub != null || subType != null || subTypeFromAntlr != null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null && subType==null && subTypeFromAntlr==null)
|
||||
if(base.isSplitWordArray)
|
||||
require(sub != null)
|
||||
// subType and subTypeFromAntlr allowed for arrays with struct element types
|
||||
// (from parser creating ARRAY(LONG) directly for 32-bit targets, or from VariousCleanups conversion)
|
||||
if(base == BaseDataType.ARRAY_SPLITW)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
@@ -136,10 +156,15 @@ class DataType private constructor(
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
|
||||
if (base != other.base || sub != other.sub || pointeeSub != other.pointeeSub) return false
|
||||
val st1 = subType
|
||||
val st2 = other.subType
|
||||
if (st1 == st2) return true
|
||||
if (st1 == null || st2 == null) return false
|
||||
return st1.sameas(st2)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, subType)
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, pointeeSub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
@@ -152,61 +177,72 @@ class DataType private constructor(
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.POINTER to DataType(BaseDataType.POINTER, null, null, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, null, null)
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
|
||||
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
|
||||
return if(splitwordarray && actualElementDt.isWord)
|
||||
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
|
||||
else {
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
fun arrayFor(elementDt: BaseDataType, memsizer: IMemSizer): DataType {
|
||||
require(elementDt != BaseDataType.POINTER) { "use arrayOfPointersTo for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) {
|
||||
memsizer.pointerBaseType
|
||||
} else elementDt
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
return DataType(BaseDataType.ARRAY, actualElementDt, null, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
|
||||
fun splitWordArrayFor(elementDt: BaseDataType): DataType {
|
||||
require(elementDt.isWord) { "split word array element type must be word" }
|
||||
return DataType(BaseDataType.ARRAY_SPLITW, elementDt, null, null)
|
||||
}
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, BaseDataType.STRUCT_INSTANCE, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, sub ?: BaseDataType.STRUCT_INSTANCE, null, identifier)
|
||||
|
||||
// array of struct instances: sub is STRUCT_INSTANCE, subType carries the resolved struct
|
||||
fun arrayOfStructs(structType: ISubType): DataType = DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, structType)
|
||||
fun arrayOfStructsFromAntlr(struct: List<String>): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null)
|
||||
DataType(BaseDataType.POINTER, dt.base, null, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
DataType(BaseDataType.POINTER, null, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, pointeeSub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
@@ -214,48 +250,85 @@ class DataType private constructor(
|
||||
// DataType Methods
|
||||
// ============================================================================
|
||||
|
||||
fun elementToArray(splitwords: Boolean = true): DataType {
|
||||
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
|
||||
else arrayFor(base, false)
|
||||
fun elementToArray(target: ICompilationTarget, splitwords: Boolean = true): DataType {
|
||||
fun pointerArray(): DataType =
|
||||
if (subType != null)
|
||||
arrayOfPointersTo(subType)
|
||||
else if (subTypeFromAntlr != null)
|
||||
arrayOfPointersFromAntlrTo(sub, subTypeFromAntlr)
|
||||
else
|
||||
arrayOfPointersTo(sub!!)
|
||||
|
||||
if(base == BaseDataType.STRUCT_INSTANCE) {
|
||||
val st = subType
|
||||
return if(st != null)
|
||||
arrayOfStructs(st)
|
||||
else
|
||||
arrayOfStructsFromAntlr(subTypeFromAntlr!!)
|
||||
}
|
||||
|
||||
if(base.isPointer)
|
||||
return pointerArray()
|
||||
|
||||
if(!target.cpu.is6502)
|
||||
return arrayFor(base, target)
|
||||
|
||||
return if (splitwords && (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR))
|
||||
splitWordArrayFor(base)
|
||||
else
|
||||
arrayFor(base, target)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
|
||||
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
|
||||
isPointerArray -> {
|
||||
if (subType != null) DataType(BaseDataType.POINTER, null, null, subType)
|
||||
else if (subTypeFromAntlr != null) DataType(BaseDataType.POINTER, null, null, null, subTypeFromAntlr)
|
||||
else DataType(BaseDataType.POINTER, pointeeSub!!, null, null)
|
||||
}
|
||||
base.isArray || base==BaseDataType.STR -> {
|
||||
if (sub == BaseDataType.STRUCT_INSTANCE && subType != null)
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, null, subType)
|
||||
val dt = forDt(sub!!)
|
||||
if (subType != null) DataType(BaseDataType.POINTER, null, null, subType) else dt
|
||||
}
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForAddressOf(msb: Boolean): DataType {
|
||||
fun typeForUntypedAddressOf(msb: Boolean, mem: IMemSizer): DataType {
|
||||
val untypedPointerType = if(mem.memorySize(BaseDataType.POINTER)>2) LONG else UWORD
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else untypedPointerType
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return UWORD
|
||||
return untypedPointerType
|
||||
if (isArray) {
|
||||
if (msb || isSplitWordArray)
|
||||
val isSplit = base == BaseDataType.ARRAY_SPLITW ||
|
||||
(isPointerArray && mem.memorySize(BaseDataType.POINTER) <= 2)
|
||||
if (msb || isSplit)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
require(elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
if (elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
return untypedPointerType
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return UWORD
|
||||
return untypedPointerType
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type ${this}"}
|
||||
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type $this"}
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
@@ -270,6 +343,13 @@ class DataType private constructor(
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
BaseDataType.POINTER -> {
|
||||
val pointee = if (subType != null) subType!!.scopedNameString
|
||||
else if (subTypeFromAntlr != null) "$subTypeFromAntlr"
|
||||
else pointeeSub!!.name.lowercase()
|
||||
"^^$pointee[]"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[]" else "$subTypeFromAntlr[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
@@ -283,9 +363,6 @@ class DataType private constructor(
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
@@ -317,14 +394,6 @@ class DataType private constructor(
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}["
|
||||
subType!=null -> "^^${subType!!.scopedNameString}["
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else -> "????? ["
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
@@ -334,6 +403,13 @@ class DataType private constructor(
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
BaseDataType.POINTER -> {
|
||||
val pointee = if (subType != null) "^^${subType!!.scopedNameString}["
|
||||
else if (subTypeFromAntlr != null) "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else "^^${pointeeSub!!.name.lowercase()}["
|
||||
pointee
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[" else "$subTypeFromAntlr["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
@@ -356,12 +432,20 @@ class DataType private constructor(
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER) || targetType.isPointerArray
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER) || targetType.isPointerArray
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD, BaseDataType.LONG) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> {
|
||||
if (isPointerArray) {
|
||||
targetType.isPointerArray && if (subType != null && targetType.subType != null) subType!!.sameas(targetType.subType!!)
|
||||
else if (subType == null && targetType.subType == null) pointeeSub == targetType.pointeeSub
|
||||
else false
|
||||
} else {
|
||||
targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
@@ -370,25 +454,24 @@ class DataType private constructor(
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.ARRAY_POINTER -> false
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
fun equalsSize(other: DataType, memsizer: IMemSizer): Boolean = base.equalsSize(other.base, memsizer)
|
||||
|
||||
/**
|
||||
* Returns the memory size in bytes.
|
||||
* Note: for pointer types, size() doesn't return the size of the pointer itself
|
||||
* but the size of the thing it points to.
|
||||
*/
|
||||
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
|
||||
memsizer.memorySize(sub)
|
||||
} else if(subType!=null) {
|
||||
subType!!.memsize(memsizer)
|
||||
} else {
|
||||
memsizer.memorySize(base)
|
||||
}
|
||||
fun size(memsizer: IMemSizer): Int = when {
|
||||
base == BaseDataType.ARRAY && sub == BaseDataType.POINTER -> memsizer.memorySize(BaseDataType.POINTER)
|
||||
sub!=null -> memsizer.memorySize(sub)
|
||||
subType!=null -> subType!!.memsize(memsizer)
|
||||
else -> memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Properties
|
||||
@@ -417,24 +500,34 @@ class DataType private constructor(
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base.isPointerArray
|
||||
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
|
||||
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isPointerArray = base == BaseDataType.ARRAY && sub == BaseDataType.POINTER
|
||||
val isBoolArray = base == BaseDataType.ARRAY && sub == BaseDataType.BOOL
|
||||
val isByteArray = base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base == BaseDataType.ARRAY && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base == BaseDataType.ARRAY && sub == BaseDataType.BYTE
|
||||
val isWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && sub == BaseDataType.WORD
|
||||
val isLongArray = base == BaseDataType.ARRAY && sub == BaseDataType.LONG
|
||||
val isFloatArray = base == BaseDataType.ARRAY && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
val isSplitWordArray = base.isSplitWordArray
|
||||
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
fun isStringly(memsizer: IMemSizer): Boolean =
|
||||
base == BaseDataType.STR ||
|
||||
(memsizer.POINTER_MEM_SIZE == 2u && base == BaseDataType.UWORD) ||
|
||||
(memsizer.POINTER_MEM_SIZE > 2u && base == BaseDataType.LONG) ||
|
||||
(base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
fun isSplitWordArray(memsizer: IMemSizer): Boolean {
|
||||
return when (base) {
|
||||
BaseDataType.ARRAY_SPLITW -> true
|
||||
BaseDataType.ARRAY if sub == BaseDataType.POINTER -> memsizer.POINTER_MEM_SIZE <= 2u
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
val isSplitUnsignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
|
||||
@@ -4,7 +4,11 @@ package prog8.code.core
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
Y,
|
||||
// m68k:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7
|
||||
}
|
||||
|
||||
enum class RegisterOrPair {
|
||||
@@ -20,7 +24,12 @@ enum class RegisterOrPair {
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R8, R9, R10, R11, R12, R13, R14, R15,
|
||||
// combined virtual registers to store 32 bits longs:
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15,
|
||||
// m68k data & address registers:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
// m68k FPU registers:
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7;
|
||||
|
||||
companion object {
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
@@ -29,6 +38,29 @@ enum class RegisterOrPair {
|
||||
CpuRegister.A -> A
|
||||
CpuRegister.X -> X
|
||||
CpuRegister.Y -> Y
|
||||
CpuRegister.D0 -> D0
|
||||
CpuRegister.D1 -> D1
|
||||
CpuRegister.D2 -> D2
|
||||
CpuRegister.D3 -> D3
|
||||
CpuRegister.D4 -> D4
|
||||
CpuRegister.D5 -> D5
|
||||
CpuRegister.D6 -> D6
|
||||
CpuRegister.D7 -> D7
|
||||
CpuRegister.A0 -> A0
|
||||
CpuRegister.A1 -> A1
|
||||
CpuRegister.A2 -> A2
|
||||
CpuRegister.A3 -> A3
|
||||
CpuRegister.A4 -> A4
|
||||
CpuRegister.A5 -> A5
|
||||
CpuRegister.A6 -> A6
|
||||
CpuRegister.FP0 -> FP0
|
||||
CpuRegister.FP1 -> FP1
|
||||
CpuRegister.FP2 -> FP2
|
||||
CpuRegister.FP3 -> FP3
|
||||
CpuRegister.FP4 -> FP4
|
||||
CpuRegister.FP5 -> FP5
|
||||
CpuRegister.FP6 -> FP6
|
||||
CpuRegister.FP7 -> FP7
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +150,15 @@ val CombinedLongRegisters = setOf(
|
||||
RegisterOrPair.R14R15
|
||||
)
|
||||
|
||||
val M68kRegisters = setOf(
|
||||
RegisterOrPair.D0, RegisterOrPair.D1, RegisterOrPair.D2, RegisterOrPair.D3,
|
||||
RegisterOrPair.D4, RegisterOrPair.D5, RegisterOrPair.D6, RegisterOrPair.D7,
|
||||
RegisterOrPair.A0, RegisterOrPair.A1, RegisterOrPair.A2, RegisterOrPair.A3,
|
||||
RegisterOrPair.A4, RegisterOrPair.A5, RegisterOrPair.A6,
|
||||
RegisterOrPair.FP0, RegisterOrPair.FP1, RegisterOrPair.FP2, RegisterOrPair.FP3,
|
||||
RegisterOrPair.FP4, RegisterOrPair.FP5, RegisterOrPair.FP6, RegisterOrPair.FP7
|
||||
)
|
||||
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
@@ -128,6 +169,8 @@ enum class OutputType {
|
||||
RAW,
|
||||
PRG,
|
||||
XEX,
|
||||
ELF,
|
||||
AMIGAHUNK,
|
||||
LIBRARY
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,67 @@ package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65C02,
|
||||
VIRTUAL
|
||||
/**
|
||||
* Identifies the target CPU type and its key characteristics that affect IR semantics.
|
||||
*
|
||||
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
|
||||
* status-bit contract (see IRInstructions.kt for the full spec):
|
||||
*
|
||||
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
|
||||
* branches that depend on the result of a previous "status-setting" instruction
|
||||
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
|
||||
* Z and N based on the full multi-byte value." Backends for these targets must
|
||||
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
|
||||
* the 16-bit value, so the IR can rely on it).
|
||||
*
|
||||
* - When `false`, multi-byte operations on this target do NOT set status bits
|
||||
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
|
||||
* low byte of a 16-bit decrement). The IR generator must therefore always emit
|
||||
* an explicit CMP/CMPI before any branch that depends on the result. Backends
|
||||
* for these targets should NOT add `ora` postambles to set Z; instead they
|
||||
* rely on the explicit CMP/CMPI emitted by the IR generator.
|
||||
*
|
||||
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
|
||||
* cascade are misleading: Z is set based on the LAST byte operated on, not
|
||||
* the full value. So we treat the contract as "no instruction sets status
|
||||
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
|
||||
* explicit compares. This avoids the trap of "this works for BYTE but
|
||||
* silently breaks for WORD/LONG".
|
||||
*/
|
||||
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
|
||||
CPU6502(false),
|
||||
|
||||
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
|
||||
CPU65C02(false),
|
||||
|
||||
/** 16/32 bit Motorola 68000. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68000(true),
|
||||
|
||||
/** 32 bit Motorola 68020. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68020(true),
|
||||
|
||||
/**
|
||||
* Virtual machine target. We use `false` here so the IR generator always
|
||||
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
|
||||
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
|
||||
* existing documentation (VirtualMachine.kt top comment) and keeps the
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
;
|
||||
|
||||
/** True for the 6502-family targets (plain 6502 and 65C02). */
|
||||
val is6502: Boolean
|
||||
get() = this == CPU6502 || this == CPU65C02
|
||||
val is68k: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
/** True for targets that use the m68k-style hardware-register calling convention (D0-D7, A0-A6, FP0-FP7). */
|
||||
val usesM68kConvention: Boolean
|
||||
get() = is68k || this == VIRTUAL
|
||||
/** True for big-endian targets. The 6502 family and the VM are little-endian. */
|
||||
val isBigEndian: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
@@ -13,8 +70,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
|
||||
val ARRAY_SIZE_LIMIT: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
@@ -28,6 +84,15 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
val defaultOutputType: OutputType
|
||||
val defaultLauncherType: CbmPrgLauncherType
|
||||
val supportsBankedCalls: Boolean
|
||||
|
||||
/**
|
||||
* True when the code generator allocates subroutine locals and per-activation intermediate values
|
||||
* in a stack frame instead of static memory, so a subroutine on a call-graph cycle can have more
|
||||
* than one live activation. False means two live activations would share the same storage.
|
||||
*/
|
||||
val supportsStackFrames: Boolean
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
@@ -35,7 +100,12 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
fun convertFloatToBytes(num: Double): List<UByte>
|
||||
fun convertBytesToFloat(bytes: List<UByte>): Double
|
||||
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean)
|
||||
/**
|
||||
* Launches an emulator to run the compiled program artifact.
|
||||
* [programFile] is the exact artifact path including its extension; launchers must use it
|
||||
* verbatim, without stripping or appending one.
|
||||
*/
|
||||
fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
package prog8.code.core
|
||||
|
||||
interface IMemSizer {
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val POINTER_MEM_SIZE: UInt
|
||||
|
||||
val pointerType: DataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) DataType.LONG else DataType.UWORD
|
||||
|
||||
val pointerBaseType: BaseDataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) BaseDataType.LONG else BaseDataType.UWORD
|
||||
|
||||
fun memorySize(dt: DataType, numElements: Int?): Int
|
||||
|
||||
fun memorySize(dt: BaseDataType): Int {
|
||||
if(dt.isPassByRef)
|
||||
return memorySize(DataType.UWORD, null) // a pointer size
|
||||
return POINTER_MEM_SIZE.toInt()
|
||||
try {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
} catch (x: NoSuchElementException) {
|
||||
throw IllegalArgumentException(x.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun isSplitWordArray(dt: DataType): Boolean {
|
||||
return dt.isSplitWordArray(this)
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,26 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
|
||||
/**
|
||||
* Report a warning for every scratch byte that is excluded from allocation by a %zpreserved
|
||||
* or %zpallowed directive. The compiler's code generators always use these fixed scratch
|
||||
* locations internally (P8ZP_SCRATCH_*), so a %zpreserved/%zpallowed range that excludes one
|
||||
* of them would let the compiler overwrite memory the user explicitly wants to protect.
|
||||
*/
|
||||
fun checkScratchConflicts(errors: IErrorReporter) {
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val scratchBytes = listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR + 1u)
|
||||
for(byte in scratchBytes) {
|
||||
val preserved = options.zpReserved.any { byte in it }
|
||||
val allowed = options.zpAllowed.any { byte in it }
|
||||
if(preserved || !allowed)
|
||||
errors.warn("zero-page address $${byte.toString(16)} is a fixed scratch location used by the compiler (P8ZP_SCRATCH_*), but it is excluded by a %zpreserved or %zpallowed directive", Position.DUMMY)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
@@ -46,12 +66,9 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun hasByteAvailable() = options.zeropage != ZeropageType.DONTUSE && free.isNotEmpty()
|
||||
fun hasWordAvailable(): Boolean {
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return false
|
||||
|
||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
return options.zeropage != ZeropageType.DONTUSE && free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
}
|
||||
|
||||
override fun allocate(name: String,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.target.VMTarget
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.name
|
||||
|
||||
|
||||
/** VICE/box16 monitor (symbol) list file name for an artifact stem. */
|
||||
fun viceMonListName(stem: String) = "$stem.vice-mon-list"
|
||||
|
||||
|
||||
/**
|
||||
* Artifact stem of a user-supplied `-o` output file name.
|
||||
* A dot at index 0 is not an extension separator: ".pgz" keeps its whole name.
|
||||
* Only applied to `-o`; the legacy program stem (source name without extension) is used verbatim.
|
||||
*/
|
||||
fun outputStemOf(name: Path): String {
|
||||
val filename = name.name
|
||||
val dotIndex = filename.lastIndexOf('.')
|
||||
return if (dotIndex <= 0) filename else filename.substring(0, dotIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* The final program artifact path: [CompilationOptions.outputFile] verbatim when set,
|
||||
* otherwise [defaultName] plus the extension for [CompilationOptions.output], in [CompilationOptions.outputDir].
|
||||
* RAW and LIBRARY both use ".bin"; AMIGAHUNK has no extension.
|
||||
* On the virtual target the artifact is the IR file, so ".p8ir" is always enforced on -o.
|
||||
*/
|
||||
fun CompilationOptions.programFile(defaultName: String): Path {
|
||||
// VMTarget's defaultOutputType = PRG is misleading: its artifact is the .p8ir file, never PRG.
|
||||
// The VM identifies the IR by its extension, so -o is not verbatim here: ".p8ir" is enforced.
|
||||
// Only the exact lowercase extension is recognized (same rule as -vm in CompilerMain).
|
||||
if(compTarget is VMTarget) {
|
||||
val ir = outputFile ?: return outputDir.resolve("$defaultName.p8ir")
|
||||
return if (ir.extension=="p8ir") ir else ir.resolveSibling("${ir.name}.p8ir")
|
||||
}
|
||||
outputFile?.let { return it }
|
||||
val filename = when(output) {
|
||||
OutputType.PRG -> "$defaultName.prg"
|
||||
OutputType.XEX -> "$defaultName.xex"
|
||||
OutputType.RAW -> "$defaultName.bin"
|
||||
OutputType.LIBRARY -> "$defaultName.bin"
|
||||
OutputType.ELF -> "$defaultName.elf"
|
||||
OutputType.AMIGAHUNK -> defaultName
|
||||
}
|
||||
return outputDir.resolve(filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* Output artifact naming.
|
||||
* Invariant: [asm] must resolve into the same directory as `CompilationOptions.outputDir`,
|
||||
* because 64tass resolves `.binary` includes relative to the generated `.asm`.
|
||||
*/
|
||||
class OutputFiles(val programFile: Path, val stem: String, val dir: Path) {
|
||||
fun asm() = dir.resolve("$stem.asm")
|
||||
fun list() = dir.resolve("$stem.list")
|
||||
fun viceMonList() = dir.resolve(viceMonListName(stem))
|
||||
fun binFile() = dir.resolve("$stem.bin")
|
||||
fun ir() = dir.resolve("$stem.p8ir")
|
||||
fun bankedCalls() = dir.resolve("$stem.bankedcalls")
|
||||
fun obj() = dir.resolve("$stem.o")
|
||||
fun linkScript() = dir.resolve("$stem.link.ld")
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Builds the output file set. Without `-o` the stem is [defaultName] verbatim
|
||||
* (outputStemOf is only applied to the `-o` name). With `-o` the stem is derived
|
||||
* from the resolved artifact name, so it also covers the virtual target appending
|
||||
* ".p8ir". [dir] is the program file's parent, or [CompilationOptions.outputDir]
|
||||
* for a bare -o filename.
|
||||
*/
|
||||
fun of(options: CompilationOptions, defaultName: String): OutputFiles {
|
||||
val programFile = options.programFile(defaultName)
|
||||
val stem = options.outputFile?.let { outputStemOf(programFile) } ?: defaultName
|
||||
val dir = programFile.parent ?: options.outputDir
|
||||
return OutputFiles(programFile, stem, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package prog8.code.cpu6502
|
||||
|
||||
// Horner ("shift-add") expansion of a multiply by a small compile-time constant, for the 6502 and
|
||||
// 65C02 backends. The runtime routines prog8_math.multiply_words / multiply_bytes cost the same
|
||||
// whatever the constant is (~442 and ~130 cycles), while this expansion costs O(bit length), so
|
||||
// for a small constant inlining wins. It generalises the hand-written prog8_math.mul_word_N /
|
||||
// mul_byte_N routines, which are themselves unrolled Horner chains (mul_word_3 is "AY = AY*2 + AY",
|
||||
// one Horner step for 11).
|
||||
//
|
||||
// The sequences live here rather than in either backend because the register bookkeeping is easy to
|
||||
// get subtly wrong and the two backends must not drift apart.
|
||||
//
|
||||
// The gate is on generated size, not on the constant's magnitude: cycles alone would argue for
|
||||
// expanding nearly every multiply and bloating every program. Budgets are the max instruction count
|
||||
// the expansion may take; the call sequence each replaces is 6 instructions.
|
||||
|
||||
private const val WORD_BUDGET = 32
|
||||
private const val BYTE_BUDGET = 24
|
||||
|
||||
// --- byte ---
|
||||
// Contract: A holds the multiplicand. Result in A. Only A and one scratch byte are live, so there
|
||||
// is no second register to keep in sync (unlike the word case, where a bare doubling leaves the
|
||||
// high byte stale in Y).
|
||||
|
||||
fun byteShiftAddExpansion(value: Int): List<String>? {
|
||||
if (value !in 3..255)
|
||||
return null
|
||||
val bitlen = 31 - Integer.numberOfLeadingZeros(value)
|
||||
val popcount = Integer.bitCount(value)
|
||||
if (1 + (bitlen-1) + 2*(popcount-1) > BYTE_BUDGET)
|
||||
return null
|
||||
val result = ArrayList<String>(1 + 3*bitlen)
|
||||
result.add("sta P8ZP_SCRATCH_REG") // keep x for the conditional adds
|
||||
for (bit in (bitlen-1) downTo 0) {
|
||||
result.add("asl a") // r = r*2
|
||||
if (value and (1 shl bit) != 0) { // r += x
|
||||
result.add("clc")
|
||||
result.add("adc P8ZP_SCRATCH_REG")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// --- word ---
|
||||
// Contract on entry: A = r.lo, Y = r.hi, P8ZP_SCRATCH_W1 = r, P8ZP_SCRATCH_W2 = x (all 16-bit,
|
||||
// little-endian: byte 0 is the low byte). On exit: A = result.lo, Y = result.hi.
|
||||
//
|
||||
// Two things this sequence must get right, both learned the hard way:
|
||||
// * the add step must store the new high byte back to W1+1 as well as to Y. Writing it only to Y
|
||||
// leaves W1+1 stale, and the next step's 'rol P8ZP_SCRATCH_W1+1' would then double the wrong
|
||||
// high byte.
|
||||
// * a trailing bare doubling (any even constant) updates A and W1+1 but not Y, so Y needs an
|
||||
// explicit reload at the end. It cannot be 'tay', because at that point A holds the low byte.
|
||||
|
||||
fun wordShiftAddExpansion(value: Int): List<String>? {
|
||||
if (value < 3)
|
||||
return null
|
||||
val bitlen = 31 - Integer.numberOfLeadingZeros(value)
|
||||
val popcount = Integer.bitCount(value)
|
||||
if (1 + 2*(bitlen-1) + 7*(popcount-1) > WORD_BUDGET)
|
||||
return null
|
||||
val result = ArrayList<String>(2 + 9*bitlen)
|
||||
for (bit in (bitlen-1) downTo 0) {
|
||||
result.add("asl a") // r = r*2
|
||||
result.add("rol P8ZP_SCRATCH_W1+1")
|
||||
if (value and (1 shl bit) != 0) { // r += x
|
||||
result.add("clc")
|
||||
result.add("adc P8ZP_SCRATCH_W2")
|
||||
result.add("sta P8ZP_SCRATCH_W1")
|
||||
result.add("lda P8ZP_SCRATCH_W1+1")
|
||||
result.add("adc P8ZP_SCRATCH_W2+1")
|
||||
result.add("sta P8ZP_SCRATCH_W1+1")
|
||||
result.add("tay")
|
||||
result.add("lda P8ZP_SCRATCH_W1")
|
||||
}
|
||||
}
|
||||
if (value and 1 == 0)
|
||||
result.add("ldy P8ZP_SCRATCH_W1+1")
|
||||
return result
|
||||
}
|
||||
@@ -13,9 +13,10 @@ import kotlin.io.path.Path
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
val userHome: Path get() = Path.of(System.getProperty("user.home"))
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
val userHome = System.getProperty("user.home")
|
||||
userHome + path.drop(1)
|
||||
userHome.toString() + path.drop(1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
@@ -35,6 +36,11 @@ object ImportFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga1200Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga1200"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = Amiga500Target.LibraryNumbers
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68020
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if (selectedEmulator == 1) {
|
||||
CopperlineEmulator.launch(programFile, quiet, "A1200", "68020", listOf("--fast", "2M", "--fpu"))
|
||||
return
|
||||
}
|
||||
if (selectedEmulator != 2) {
|
||||
System.err.println("The amiga1200 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when (this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
VamosEmulator.launch(programFile, quiet, cpuStr)
|
||||
return
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga500Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga500"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = mapOf(
|
||||
1 to "Sys", // exec
|
||||
2 to "DOS",
|
||||
3 to "Gfx",
|
||||
4 to "Intuition",
|
||||
5 to "GadTools",
|
||||
6 to "Layers",
|
||||
7 to "Asl",
|
||||
8 to "ConsoleDevice",
|
||||
9 to "Utility",
|
||||
10 to "Expansion",
|
||||
11 to "Icon",
|
||||
12 to "Workbench",
|
||||
13 to "Diskfont",
|
||||
14 to "IFFParse",
|
||||
15 to "Input",
|
||||
16 to "Keymap",
|
||||
17 to "Locale",
|
||||
18 to "Timer",
|
||||
19 to "LowLevel",
|
||||
20 to "Math",
|
||||
21 to "MathIeeeSingBas",
|
||||
22 to "MathIeeeSingTrans",
|
||||
23 to "MathIeeeDoubBas",
|
||||
24 to "MathIeeeDoubTrans",
|
||||
25 to "MathTrans",
|
||||
26 to "NV",
|
||||
27 to "RealTime",
|
||||
28 to "Translator",
|
||||
29 to "RexxSys",
|
||||
30 to "Cx",
|
||||
31 to "DataTypes",
|
||||
32 to "Disk",
|
||||
33 to "AmigaGuide",
|
||||
34 to "ARexx",
|
||||
35 to "BattClock",
|
||||
36 to "BattMem",
|
||||
// from here, non-standard libraries are listed
|
||||
100 to "AsyncIO",
|
||||
101 to "AHI", // ahi.device base (amiga1200 target only, 68020+, used by the low-level ahi.* LVOs)
|
||||
)
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68000
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if (selectedEmulator == 1) {
|
||||
CopperlineEmulator.launch(programFile, quiet, "A500", "68000", listOf("--slow", "512K"))
|
||||
return
|
||||
}
|
||||
if (selectedEmulator != 2) {
|
||||
System.err.println("The amiga500 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when (this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
VamosEmulator.launch(programFile, quiet, cpuStr)
|
||||
return
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,17 @@ import java.nio.file.Path
|
||||
|
||||
class C128Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
@@ -26,8 +29,9 @@ class C128Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
@@ -51,7 +55,7 @@ class C128Target: ICompilationTarget,
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c128 target only supports the main emulator (Vice).")
|
||||
return
|
||||
@@ -60,9 +64,9 @@ class C128Target: ICompilationTarget,
|
||||
if(!quiet)
|
||||
println("\nStarting C-128 emulator x128...")
|
||||
|
||||
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val viceMonlist = programFile.resolveSibling(viceMonListName(outputStemOf(programFile)))
|
||||
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist.toString(),
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programFile.toString())
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
|
||||
@@ -9,19 +9,20 @@ import java.nio.file.Path
|
||||
|
||||
class C64Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
|
||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +30,9 @@ class C64Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
@@ -55,7 +57,7 @@ class C64Target: ICompilationTarget,
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c64 target only supports the main emulator (Vice).")
|
||||
return
|
||||
@@ -65,9 +67,9 @@ class C64Target: ICompilationTarget,
|
||||
if(!quiet)
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
|
||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val viceMonlist = programFile.resolveSibling(viceMonListName(outputStemOf(programFile)))
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist.toString(),
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programFile.toString())
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
@@ -96,6 +98,9 @@ val CompilationTargets = listOf(
|
||||
C128Target.NAME,
|
||||
Cx16Target.NAME,
|
||||
PETTarget.NAME,
|
||||
Qemu68kTarget.NAME,
|
||||
Amiga500Target.NAME,
|
||||
Amiga1200Target.NAME,
|
||||
VMTarget.NAME
|
||||
)
|
||||
|
||||
@@ -104,6 +109,9 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
PETTarget.NAME -> PETTarget()
|
||||
Qemu68kTarget.NAME -> Qemu68kTarget()
|
||||
Amiga500Target.NAME -> Amiga500Target()
|
||||
Amiga1200Target.NAME -> Amiga1200Target()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import prog8.code.core.*
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.ConfigurableZeropage
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
@@ -15,19 +16,22 @@ import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
class ConfigFileTarget(
|
||||
override val name: String,
|
||||
override val supportsBankedCalls: Boolean,
|
||||
override val defaultEncoding: Encoding,
|
||||
override val cpu: CpuType,
|
||||
override val supportsStackFrames: Boolean,
|
||||
override val PROGRAM_LOAD_ADDRESS: UInt,
|
||||
override val PROGRAM_MEMTOP_ADDRESS: UInt,
|
||||
override val STARTUP_CODE_RESERVED_SIZE: UInt,
|
||||
override val BSSHIGHRAM_START: UInt,
|
||||
override val BSSHIGHRAM_END: UInt,
|
||||
override val BSSGOLDENRAM_START: UInt,
|
||||
override val BSSGOLDENRAM_END: UInt,
|
||||
override val defaultOutputType: OutputType,
|
||||
override val defaultLauncherType: CbmPrgLauncherType,
|
||||
override val libraryPath: Path,
|
||||
override val customLauncher: List<String>,
|
||||
override val additionalAssemblerOptions: List<String>,
|
||||
val varsAddress: UInt? = null,
|
||||
val ioAddresses: List<UIntRange>,
|
||||
val zpScratchB1: UInt,
|
||||
val zpScratchReg: UInt,
|
||||
@@ -37,8 +41,13 @@ class ConfigFileTarget(
|
||||
val virtualregistersStart: UInt,
|
||||
val zpFullsafe: List<UIntRange>,
|
||||
val zpKernalsafe: List<UIntRange>,
|
||||
val zpBasicsafe: List<UIntRange>
|
||||
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
|
||||
val zpBasicsafe: List<UIntRange>,
|
||||
val pointerSize: Int = 2
|
||||
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8u, pointerSize.toUInt()) {
|
||||
|
||||
init {
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -65,6 +74,12 @@ class ConfigFileTarget(
|
||||
return value.toUInt()
|
||||
}
|
||||
|
||||
private fun parsePointerSize(props: Properties): Int {
|
||||
val pointerSize = parseInt(props.getProperty("pointer_size", "2")).toInt()
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
return pointerSize
|
||||
}
|
||||
|
||||
private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> {
|
||||
val rangesStr = props.getString(key)
|
||||
if(rangesStr.isBlank())
|
||||
@@ -96,9 +111,12 @@ class ConfigFileTarget(
|
||||
CpuType.valueOf("CPU$cpuString")
|
||||
}
|
||||
val ioAddresses = parseAddressRanges("io_regions", props)
|
||||
val zpFullsafe = parseAddressRanges("zp_fullsafe", props)
|
||||
val zpKernalsafe = parseAddressRanges("zp_kernalsafe", props)
|
||||
val zpBasicsafe = parseAddressRanges("zp_basicsafe", props)
|
||||
// The m68k has no zero page, so the zeropage settings below don't apply to an m68k
|
||||
// target and are not required in its config file.
|
||||
fun zpSetting(name: String) = if(cpuType.is68k) 0u else props.getInteger(name)
|
||||
val zpFullsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_fullsafe", props)
|
||||
val zpKernalsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_kernalsafe", props)
|
||||
val zpBasicsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_basicsafe", props)
|
||||
|
||||
val libraryPath = expandTilde(Path(props.getString("library")))
|
||||
if(!libraryPath.isDirectory())
|
||||
@@ -110,34 +128,47 @@ class ConfigFileTarget(
|
||||
(customLauncherStr+"\n").lines().map { it.trimEnd() }
|
||||
else emptyList()
|
||||
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
|
||||
val pointerSize = parsePointerSize(props)
|
||||
val outputTypeString = props.getProperty("output_type", "PRG")
|
||||
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
|
||||
val launcherString = props.getProperty("launcher", "BASIC")
|
||||
val defaultLauncherType = try {
|
||||
CbmPrgLauncherType.valueOf(launcherString.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CbmPrgLauncherType.BASIC
|
||||
}
|
||||
|
||||
val varsAddress = props.getProperty("vars_address")?.let { parseInt(it) }
|
||||
|
||||
return ConfigFileTarget(
|
||||
configfile.nameWithoutExtension,
|
||||
props.getProperty("supports_banked_calls", "false").toBoolean(),
|
||||
Encoding.entries.first { it.prefix==props.getString("encoding") },
|
||||
cpuType,
|
||||
props.getProperty("supports_stack_frames", cpuType.is68k.toString()).toBoolean(),
|
||||
props.getInteger("load_address"),
|
||||
props.getInteger("memtop"),
|
||||
0u, // used only in a very specific error condition check in a certain scenario...
|
||||
props.getInteger("bss_highram_start"),
|
||||
props.getInteger("bss_highram_end"),
|
||||
props.getInteger("bss_goldenram_start"),
|
||||
props.getInteger("bss_goldenram_end"),
|
||||
defaultOutputType,
|
||||
defaultLauncherType,
|
||||
libraryPath,
|
||||
customLauncher,
|
||||
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
|
||||
varsAddress,
|
||||
ioAddresses,
|
||||
props.getInteger("zp_scratch_b1"),
|
||||
props.getInteger("zp_scratch_reg"),
|
||||
props.getInteger("zp_scratch_w1"),
|
||||
props.getInteger("zp_scratch_w2"),
|
||||
props.getInteger("zp_scratch_ptr"),
|
||||
props.getInteger("virtual_registers"),
|
||||
zpSetting("zp_scratch_b1"),
|
||||
zpSetting("zp_scratch_reg"),
|
||||
zpSetting("zp_scratch_w1"),
|
||||
zpSetting("zp_scratch_w2"),
|
||||
zpSetting("zp_scratch_ptr"),
|
||||
zpSetting("virtual_registers"),
|
||||
zpFullsafe,
|
||||
zpKernalsafe,
|
||||
zpBasicsafe,
|
||||
pointerSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -146,20 +177,22 @@ class ConfigFileTarget(
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
|
||||
override val POINTER_MEM_SIZE = pointerSize.toUInt()
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("floats")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("floats")
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
throw IllegalArgumentException("Custom compiler target cannot automatically launch an emulator. Do this manually.")
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it }
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = ConfigurableZeropage(
|
||||
// the m68k has no zero page, so the zp scratch addresses in the config file don't apply there
|
||||
zeropage = if(cpu.is68k) M68kZeropage(compilerOptions) else ConfigurableZeropage(
|
||||
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr,
|
||||
virtualregistersStart,
|
||||
zpBasicsafe,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package prog8.code.target
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.copyTo
|
||||
import kotlin.io.path.createDirectories
|
||||
|
||||
object CopperlineEmulator {
|
||||
fun launch(programFile: Path, quiet: Boolean, model: String, cpu: String, memoryArgs: List<String>) {
|
||||
val home = System.getProperty("user.home")
|
||||
val sourceHdName = if (model == "A500") "Harddisk500" else "Harddisk1200"
|
||||
val cmdDir = if (model == "A500") "c" else "C"
|
||||
val libsDir = if (model == "A500") "libs" else "Libs"
|
||||
val libs = listOf("diskfont.library", "iffparse.library", "lowlevel.library", "rexxsyslib.library", "asyncio.library", "icon.library")
|
||||
|
||||
val tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "prog8-copperline-$model")
|
||||
tempDir.createDirectories()
|
||||
val progName = programFile.fileName.toString()
|
||||
val stagedProg = tempDir.resolve(progName)
|
||||
programFile.copyTo(stagedProg, overwrite = true)
|
||||
|
||||
val srcHd = File("$home/Documents/FS-UAE/Hard Drives/$sourceHdName")
|
||||
if (!srcHd.isDirectory) System.err.println("Warning: source hard disk folder not found: ${srcHd}; skipping file staging.")
|
||||
for (lib in libs) {
|
||||
val src = File(srcHd, "$libsDir/$lib")
|
||||
if (src.exists()) src.toPath().copyTo(tempDir.resolve(lib), overwrite = true)
|
||||
}
|
||||
val commands = if (model == "A500") listOf("Dir","List","Makedir","Assign","SetPatch","Mount","CPU","Echo","Avail") else listOf("Dir","List","MakeDir","Assign","SetPatch","Mount","CPU","Echo","Avail")
|
||||
for (cmd in commands) {
|
||||
val src = File(srcHd, "$cmdDir/$cmd")
|
||||
if (src.exists()) src.toPath().copyTo(tempDir.resolve(cmd), overwrite = true)
|
||||
}
|
||||
val ramHandlerSrc = File(srcHd, "l/Ram-Handler")
|
||||
if (model == "A500" && ramHandlerSrc.exists()) ramHandlerSrc.toPath().copyTo(tempDir.resolve("Ram-Handler"), overwrite = true)
|
||||
val sysCfgSrc = File(srcHd, "Devs/system-configuration")
|
||||
if (sysCfgSrc.exists()) sysCfgSrc.toPath().copyTo(tempDir.resolve("system-configuration"), overwrite = true)
|
||||
|
||||
val ksDirs = listOf(
|
||||
File("${System.getProperty("user.home")}/Documents/FS-UAE/Kickstarts"),
|
||||
File("${System.getProperty("user.home")}/Documents/Kickstarts")
|
||||
)
|
||||
val ksDir = ksDirs.firstOrNull { it.isDirectory } ?: ksDirs.first()
|
||||
val candidates = ksDirs.filter { it.isDirectory }.flatMap { d -> d.listFiles()?.filter { it.name.endsWith(".rom", ignoreCase = true) && it.name.contains(model, ignoreCase = true) } ?: emptyList() }
|
||||
val preferredVersion = if (model == "A500") "v1.3" else "v3.1"
|
||||
val rom = candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && it.name.contains("[!", ignoreCase = true) }
|
||||
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
|
||||
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) }
|
||||
if (rom == null) {
|
||||
System.err.println("Warning: no Kickstart ROM for $model found in ${ksDir}; using bundled AROS.")
|
||||
}
|
||||
val cmd = mutableListOf(
|
||||
"copperline",
|
||||
"--factory",
|
||||
"--model", model,
|
||||
"--cpu", cpu
|
||||
)
|
||||
cmd.addAll(memoryArgs)
|
||||
if (rom != null) cmd.add(rom.absolutePath)
|
||||
cmd.addAll(listOf(
|
||||
"--run", stagedProg.toString()
|
||||
))
|
||||
if (!quiet) println("Launching Copperline...")
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch (_: Exception) {
|
||||
System.err.println("Cannot find 'copperline'. Install it from https://copperline.dev/")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,17 @@ import java.nio.file.Path
|
||||
|
||||
class Cx16Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "cx16"
|
||||
@@ -26,8 +29,9 @@ class Cx16Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
|
||||
@@ -51,7 +55,7 @@ class Cx16Target: ICompilationTarget,
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
val emulator: String
|
||||
val extraArgs: List<String>
|
||||
|
||||
@@ -62,7 +66,7 @@ class Cx16Target: ICompilationTarget,
|
||||
}
|
||||
2 -> {
|
||||
emulator = "box16"
|
||||
extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString()))
|
||||
extraArgs = listOf("-sym", programFile.resolveSibling(viceMonListName(outputStemOf(programFile))).toString())
|
||||
}
|
||||
else -> {
|
||||
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
||||
@@ -73,7 +77,7 @@ class Cx16Target: ICompilationTarget,
|
||||
if(!quiet)
|
||||
println("\nStarting Commander X16 emulator $emulator...")
|
||||
|
||||
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", programFile.toString()) + extraArgs
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package prog8.code.target
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.copyTo
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
object FsUaeEmulator {
|
||||
fun launch(
|
||||
programFile: Path,
|
||||
quiet: Boolean,
|
||||
model: String,
|
||||
preferredVersion: String,
|
||||
romHint: String,
|
||||
sourceHdName: String,
|
||||
cmdDir: String,
|
||||
libsDir: String,
|
||||
commands: List<String>,
|
||||
libs: List<String>,
|
||||
extraFiles: List<Pair<String, String>>,
|
||||
startupSequence: String
|
||||
) {
|
||||
val home = System.getProperty("user.home")
|
||||
val ksDir = File("$home/Documents/FS-UAE/Kickstarts")
|
||||
val candidates = ksDir.listFiles()?.filter { it.name.endsWith(".rom", ignoreCase = true) && it.name.contains(model, ignoreCase = true) } ?: emptyList()
|
||||
val rom = candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && it.name.contains("[!", ignoreCase = true) }
|
||||
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
|
||||
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) }
|
||||
?: candidates.firstOrNull { !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
|
||||
?: candidates.firstOrNull()
|
||||
?: ksDir.listFiles()?.firstOrNull { it.name.contains(romHint, ignoreCase = true) && it.name.endsWith(".rom", ignoreCase = true) }
|
||||
if (rom == null) {
|
||||
System.err.println("Cannot find a Kickstart ROM for $model in ${ksDir}")
|
||||
return
|
||||
}
|
||||
val tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "prog8-fsuae-$model")
|
||||
tempDir.createDirectories()
|
||||
val hdDir = tempDir.resolve("hd")
|
||||
hdDir.resolve("S").createDirectories()
|
||||
hdDir.resolve("C").createDirectories()
|
||||
hdDir.resolve("L").createDirectories()
|
||||
hdDir.resolve("Libs").createDirectories()
|
||||
hdDir.resolve("Devs").createDirectories()
|
||||
val progName = programFile.fileName.toString()
|
||||
programFile.copyTo(hdDir.resolve(progName), overwrite = true)
|
||||
val srcHd = File("$home/Documents/FS-UAE/Hard Drives/$sourceHdName")
|
||||
for (cmd in commands) {
|
||||
val src = File(srcHd, "$cmdDir/$cmd")
|
||||
if (src.exists()) src.toPath().copyTo(hdDir.resolve("C/$cmd"), overwrite = true)
|
||||
}
|
||||
for (lib in libs) {
|
||||
val src = File(srcHd, "$libsDir/$lib")
|
||||
if (src.exists()) src.toPath().copyTo(hdDir.resolve("Libs/$lib"), overwrite = true)
|
||||
}
|
||||
for ((srcRel, dstRel) in extraFiles) {
|
||||
val src = File(srcHd, srcRel)
|
||||
if (src.exists()) {
|
||||
val dst = hdDir.resolve(dstRel)
|
||||
dst.parent.createDirectories()
|
||||
src.toPath().copyTo(dst, overwrite = true)
|
||||
}
|
||||
}
|
||||
hdDir.resolve("S/Startup-Sequence").writeText(startupSequence.replace("\$progName", progName))
|
||||
val memoryArgs = if (model == "A500") listOf("--slow_memory=512") else listOf("--fast_memory=2048")
|
||||
val cmd = listOf(
|
||||
"fs-uae",
|
||||
"--amiga_model=$model",
|
||||
"--kickstart_file=${rom.absolutePath}",
|
||||
"--hard_drive_0=${hdDir}",
|
||||
"--automatic_input_grab=0",
|
||||
"--window_width=1280",
|
||||
"--window_height=1024",
|
||||
"--floppy_drive_volume=8",
|
||||
"--floppy_drive_volume_empty=8"
|
||||
) + memoryArgs
|
||||
if (!quiet) println("Launching FS-UAE...")
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch (_: Exception) {
|
||||
System.err.println("Cannot find 'fs-uae'. Install it from https://fs-uae.net/")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
companion object {
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
const val FLOAT_MEM_SIZE = 5
|
||||
const val FLOAT_MEM_SIZE = 5u
|
||||
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
@@ -73,4 +73,4 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,36 +4,42 @@ import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IMemSizer
|
||||
|
||||
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
|
||||
internal class NormalMemSizer(override val FLOAT_MEM_SIZE: UInt, override val POINTER_MEM_SIZE: UInt): IMemSizer {
|
||||
|
||||
private val pointerSize = POINTER_MEM_SIZE.toInt()
|
||||
private val floatSize = FLOAT_MEM_SIZE.toInt()
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if(dt.isPointerArray)
|
||||
return 2 * numElements!! // array of pointers is just array of uwords
|
||||
return pointerSize * numElements!! // array of pointers
|
||||
else if(dt.isArray) {
|
||||
if(numElements==null) return 2 // treat it as a pointer size
|
||||
if(numElements==null) return pointerSize // treat it as a pointer size
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.STR, BaseDataType.POINTER -> numElements * pointerSize
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT-> numElements * floatsize
|
||||
BaseDataType.FLOAT-> numElements * floatSize
|
||||
BaseDataType.STRUCT_INSTANCE -> numElements * (dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance array has unresolved subtype"))
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
else if (dt.isString) {
|
||||
return numElements // treat it as the size of the given string with the length
|
||||
?: 2 // treat it as the size to store a string pointer
|
||||
?: pointerSize // treat it as the size to store a string pointer
|
||||
}
|
||||
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isFloat -> floatsize * (numElements ?: 1)
|
||||
dt.isFloat -> floatSize * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isPointer -> 2 // pointer is just a uword
|
||||
dt.isStructInstance -> dt.subType!!.memsize(this)
|
||||
dt.isWord -> 2 * (numElements ?: 1)
|
||||
dt.isPointer -> pointerSize * (numElements ?: 1)
|
||||
dt.isStructInstance -> dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance has unresolved subtype")
|
||||
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> 2 * (numElements ?: 1)
|
||||
else -> throw IllegalArgumentException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,17 @@ import java.nio.file.Path
|
||||
|
||||
class PETTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val supportsStackFrames = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "pet32"
|
||||
@@ -25,8 +28,9 @@ class PETTarget: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
|
||||
@@ -50,7 +54,7 @@ class PETTarget: ICompilationTarget,
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The pet target only supports the main emulator (Vice).")
|
||||
return
|
||||
@@ -59,9 +63,9 @@ class PETTarget: ICompilationTarget,
|
||||
if(!quiet)
|
||||
println("\nStarting PET emulator...")
|
||||
|
||||
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val viceMonlist = programFile.resolveSibling(viceMonListName(outputStemOf(programFile)))
|
||||
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist.toString(),
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programFile.toString())
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* For now target a m68020 cpu
|
||||
* Eventually the goal is to be able to create programs for the Amiga A1200 (68020 cpu) or A500 (68000 cpu)
|
||||
*/
|
||||
|
||||
class Qemu68kTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val supportsStackFrames = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.ELF
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "qemu68k"
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68020
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x10000u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x00100000u // TODO hardcoded at 1 Mb of RAM for now... it starts at $0
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
override val BSSGOLDENRAM_START = 0u // not actually used
|
||||
override val BSSGOLDENRAM_END = 0u // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The qemu68k target only supports the main emulator (Qemu).")
|
||||
return
|
||||
}
|
||||
if(!programFile.toFile().exists()) {
|
||||
System.err.println("No program file found: $programFile")
|
||||
return
|
||||
}
|
||||
val cpuStr = this.cpu.toString().lowercase()
|
||||
|
||||
val cmd = listOf(
|
||||
"qemu-system-m68k",
|
||||
"-M", "virt",
|
||||
"-cpu", cpuStr,
|
||||
"-m", "1M",
|
||||
"-kernel", programFile.toString(),
|
||||
"-nographic"
|
||||
)
|
||||
if(!quiet) {
|
||||
println("Launching QEMU (press Ctrl-A X to exit)...")
|
||||
}
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch (_: java.io.IOException) {
|
||||
System.err.println("Cannot launch qemu-system-m68k. Install it via your package manager, e.g.:")
|
||||
System.err.println(" sudo apt install qemu-system-m68k # Debian/Ubuntu")
|
||||
System.err.println(" sudo pacman -S qemu-system-m68k # Arch Linux")
|
||||
System.err.println("or build it from source: https://www.qemu.org/download/")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address>=0xff000000u
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,35 +3,38 @@ package prog8.code.target
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.isReadable
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class VMTarget: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
|
||||
IMemSizer by NormalMemSizer(8u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val supportsStackFrames = false
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "virtual"
|
||||
const val FLOAT_MEM_SIZE = 8 // 64-bits double
|
||||
const val RAM_SIZE = 16 * 1024 * 1024 // 16 MB
|
||||
}
|
||||
|
||||
override val cpu = CpuType.VIRTUAL
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
|
||||
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
|
||||
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 65535u
|
||||
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffffu
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
@@ -67,21 +70,24 @@ class VMTarget: ICompilationTarget,
|
||||
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
launchEmulatorWithTrace(programNameWithPath, quiet, traceEnabled = false)
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
launchEmulatorWithTrace(programFile, quiet, traceEnabled = false)
|
||||
}
|
||||
|
||||
fun launchEmulatorWithTrace(programNameWithPath: Path, quiet: Boolean, traceEnabled: Boolean) {
|
||||
/**
|
||||
* Runs the given .p8ir file in the embedded virtual machine.
|
||||
* The caller must pass the exact path; no fallback probing happens here, the -vm CLI flow resolves a bare name itself.
|
||||
*/
|
||||
fun launchEmulatorWithTrace(programFile: Path, quiet: Boolean, traceEnabled: Boolean) {
|
||||
if(!quiet)
|
||||
println("\nStarting Virtual Machine...")
|
||||
|
||||
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
|
||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
|
||||
if(withExt.isReadable())
|
||||
vm.runProgram(withExt.readText(), quiet, traceEnabled)
|
||||
if(programFile.isReadable())
|
||||
vm.runProgram(programFile.readText(), quiet, traceEnabled)
|
||||
else
|
||||
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
|
||||
throw java.nio.file.NoSuchFileException(programFile.toString(), null, "not a .p8ir file")
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = false
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package prog8.code.target
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
object VamosEmulator {
|
||||
fun launch(programFile: Path, quiet: Boolean, cpu: String) {
|
||||
val cmd = listOf("vamos", "--cpu", cpu, programFile.toString())
|
||||
if (!quiet) println("Launching Amitools's Vamos...")
|
||||
val pb = ProcessBuilder(cmd).inheritIO()
|
||||
try {
|
||||
pb.start().waitFor()
|
||||
} catch (_: Exception) {
|
||||
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import java.io.CharConversionException
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to Unicode
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package prog8.code.target.zp
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.Zeropage
|
||||
|
||||
/**
|
||||
* The m68k has no zero page, so there are no scratch locations to hand out.
|
||||
* Any attempt to use one is a bug in the code generator.
|
||||
*/
|
||||
class M68kZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
override val SCRATCH_B1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_REG: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W1: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W2: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
override val SCRATCH_PTR: UInt
|
||||
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
|
||||
}
|
||||
@@ -32,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0xb1u..0xbau) // TODO more?
|
||||
// Only scratch register area is safe for transient use.
|
||||
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
|
||||
// User variables requiring persistent storage must NOT be placed here.
|
||||
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -83,4 +86,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import prog8.code.core.*
|
||||
* Returns standard memory sizes for all data types.
|
||||
*/
|
||||
object DummyMemsizer : IMemSizer {
|
||||
override val FLOAT_MEM_SIZE: UInt = 4u
|
||||
override val POINTER_MEM_SIZE: UInt = 2u
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
@@ -18,6 +21,8 @@ object DummyMemsizer : IMemSizer {
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT -> numElements * 5
|
||||
BaseDataType.POINTER -> numElements * 2
|
||||
BaseDataType.STRUCT_INSTANCE -> numElements * (dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance array has unresolved subtype"))
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class ErrorReporterForTests(
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
val printedErrors = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
@@ -47,7 +48,7 @@ class ErrorReporterForTests(
|
||||
!errors.any { ":${position.line}:" in it }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
// prints nothing in tests
|
||||
printedErrors.add(errormessage)
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.target.ConfigFileTarget
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
class TestConfigFileTarget: FunSpec({
|
||||
fun loadTarget(extraProperties: String = ""): ConfigFileTarget {
|
||||
val directory = Files.createTempDirectory("prog8-config-target")
|
||||
val library = Files.createDirectories(directory.resolve("library"))
|
||||
val config = directory.resolve("target.properties")
|
||||
config.writeText(
|
||||
"""
|
||||
cpu = 65C02
|
||||
encoding = iso
|
||||
load_address = 2048
|
||||
memtop = 65535
|
||||
bss_highram_start = 0
|
||||
bss_highram_end = 0
|
||||
bss_goldenram_start = 0
|
||||
bss_goldenram_end = 0
|
||||
zp_scratch_b1 = 240
|
||||
zp_scratch_reg = 241
|
||||
zp_scratch_w1 = 242
|
||||
zp_scratch_w2 = 244
|
||||
zp_scratch_ptr = 246
|
||||
virtual_registers = 2
|
||||
zp_fullsafe = 34-239
|
||||
zp_kernalsafe = 34-239
|
||||
zp_basicsafe = 34-239
|
||||
io_regions =
|
||||
""".trimIndent() + "\nlibrary = $library\n$extraProperties"
|
||||
)
|
||||
return try {
|
||||
ConfigFileTarget.fromConfigFile(config)
|
||||
} finally {
|
||||
directory.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
test("legacy config defaults to 2-byte pointers and 256 element arrays") {
|
||||
val target = loadTarget()
|
||||
|
||||
target.POINTER_MEM_SIZE shouldBe 2u
|
||||
target.memorySize(BaseDataType.POINTER) shouldBe 2
|
||||
target.ARRAY_SIZE_LIMIT shouldBe 256u
|
||||
}
|
||||
|
||||
test("config supports 4-byte pointers") {
|
||||
val target = loadTarget("pointer_size = 4")
|
||||
|
||||
target.POINTER_MEM_SIZE shouldBe 4u
|
||||
target.memorySize(BaseDataType.POINTER) shouldBe 4
|
||||
target.ARRAY_SIZE_LIMIT shouldBe 256u
|
||||
}
|
||||
|
||||
test("config rejects unsupported pointer sizes") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
loadTarget("pointer_size = 3")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -23,31 +23,31 @@ class TestConversions: FunSpec({
|
||||
}
|
||||
|
||||
test("toHex for 16-255 (hex with $)") {
|
||||
16.toHex() shouldBe "\$10"
|
||||
255.toHex() shouldBe "\$ff"
|
||||
16.toHex() shouldBe $$"$10"
|
||||
255.toHex() shouldBe $$"$ff"
|
||||
}
|
||||
|
||||
test("toHex for 256-65535 (4 digit hex)") {
|
||||
256.toHex() shouldBe "\$0100"
|
||||
65535.toHex() shouldBe "\$ffff"
|
||||
256.toHex() shouldBe $$"$0100"
|
||||
65535.toHex() shouldBe $$"$ffff"
|
||||
}
|
||||
|
||||
test("toHex for larger numbers (8 digit hex)") {
|
||||
65536.toHex() shouldBe "\$00010000"
|
||||
0x12345678.toHex() shouldBe "\$12345678"
|
||||
65536.toHex() shouldBe $$"$00010000"
|
||||
0x12345678.toHex() shouldBe $$"$12345678"
|
||||
}
|
||||
|
||||
test("toHex for negative numbers") {
|
||||
(-1).toHex() shouldBe "-1"
|
||||
(-255).toHex() shouldBe "-\$ff"
|
||||
Int.MIN_VALUE.toHex() shouldBe "\$80000000"
|
||||
(-255).toHex() shouldBe $$"-$ff"
|
||||
Int.MIN_VALUE.toHex() shouldBe $$"$80000000"
|
||||
}
|
||||
|
||||
test("UInt.toHex") {
|
||||
0u.toHex() shouldBe "0"
|
||||
15u.toHex() shouldBe "15"
|
||||
255u.toHex() shouldBe "\$ff"
|
||||
65535u.toHex() shouldBe "\$ffff"
|
||||
255u.toHex() shouldBe $$"$ff"
|
||||
65535u.toHex() shouldBe $$"$ffff"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -2,28 +2,36 @@ package prog8tests.codecore
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.booleans.shouldBeFalse
|
||||
import io.kotest.matchers.booleans.shouldBeTrue
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.Amiga500Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for DataType and BaseDataType classes in codeCore.
|
||||
* Focus on type predicates, relationships, and key operations.
|
||||
*/
|
||||
class TestDataType: FunSpec({
|
||||
val tgt = Cx16Target()
|
||||
|
||||
val dummyMemSizer = object : IMemSizer {
|
||||
override val FLOAT_MEM_SIZE: UInt = 4u
|
||||
override val POINTER_MEM_SIZE: UInt = 2u
|
||||
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray) return 2 * numElements!!
|
||||
if (dt.isArray || dt.isSplitWordArray) {
|
||||
if (dt.isArray || dt.isSplitWordArray(this)) {
|
||||
require(numElements != null)
|
||||
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
|
||||
BaseDataType.POINTER -> numElements * 2
|
||||
BaseDataType.STRUCT_INSTANCE -> numElements * (dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance array has unresolved subtype"))
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
@@ -45,61 +53,74 @@ class TestDataType: FunSpec({
|
||||
// ============================================================================
|
||||
|
||||
test("isByte extension") {
|
||||
BaseDataType.UBYTE.isByte.shouldBeTrue()
|
||||
BaseDataType.BYTE.isByte.shouldBeTrue()
|
||||
BaseDataType.UWORD.isByte.shouldBeFalse()
|
||||
BaseDataType.WORD.isByte.shouldBeFalse()
|
||||
BaseDataType.UBYTE.isByte shouldBe true
|
||||
BaseDataType.BYTE.isByte shouldBe true
|
||||
BaseDataType.UWORD.isByte shouldBe false
|
||||
BaseDataType.WORD.isByte shouldBe false
|
||||
}
|
||||
|
||||
test("isByteOrBool extension") {
|
||||
BaseDataType.UBYTE.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.BYTE.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.BOOL.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.UWORD.isByteOrBool.shouldBeFalse()
|
||||
BaseDataType.UBYTE.isByteOrBool shouldBe true
|
||||
BaseDataType.BYTE.isByteOrBool shouldBe true
|
||||
BaseDataType.BOOL.isByteOrBool shouldBe true
|
||||
BaseDataType.UWORD.isByteOrBool shouldBe false
|
||||
}
|
||||
|
||||
test("isWord extension") {
|
||||
BaseDataType.UWORD.isWord.shouldBeTrue()
|
||||
BaseDataType.WORD.isWord.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isWord.shouldBeFalse()
|
||||
BaseDataType.UWORD.isWord shouldBe true
|
||||
BaseDataType.WORD.isWord shouldBe true
|
||||
BaseDataType.UBYTE.isWord shouldBe false
|
||||
}
|
||||
|
||||
test("isInteger extension") {
|
||||
BaseDataType.UBYTE.isInteger.shouldBeTrue()
|
||||
BaseDataType.WORD.isInteger.shouldBeTrue()
|
||||
BaseDataType.LONG.isInteger.shouldBeTrue()
|
||||
BaseDataType.FLOAT.isInteger.shouldBeFalse()
|
||||
BaseDataType.UBYTE.isInteger shouldBe true
|
||||
BaseDataType.WORD.isInteger shouldBe true
|
||||
BaseDataType.LONG.isInteger shouldBe true
|
||||
BaseDataType.FLOAT.isInteger shouldBe false
|
||||
}
|
||||
|
||||
test("isNumeric extension") {
|
||||
BaseDataType.UBYTE.isNumeric.shouldBeTrue()
|
||||
BaseDataType.FLOAT.isNumeric.shouldBeTrue()
|
||||
BaseDataType.BOOL.isNumeric.shouldBeFalse()
|
||||
BaseDataType.UBYTE.isNumeric shouldBe true
|
||||
BaseDataType.FLOAT.isNumeric shouldBe true
|
||||
BaseDataType.BOOL.isNumeric shouldBe false
|
||||
}
|
||||
|
||||
test("isSigned extension") {
|
||||
BaseDataType.BYTE.isSigned.shouldBeTrue()
|
||||
BaseDataType.WORD.isSigned.shouldBeTrue()
|
||||
BaseDataType.LONG.isSigned.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isSigned.shouldBeFalse()
|
||||
BaseDataType.BYTE.isSigned shouldBe true
|
||||
BaseDataType.WORD.isSigned shouldBe true
|
||||
BaseDataType.LONG.isSigned shouldBe true
|
||||
BaseDataType.UBYTE.isSigned shouldBe false
|
||||
}
|
||||
|
||||
test("isArray extension") {
|
||||
BaseDataType.ARRAY.isArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_SPLITW.isArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_POINTER.isArray.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isArray.shouldBeFalse()
|
||||
BaseDataType.ARRAY.isArray shouldBe true
|
||||
BaseDataType.ARRAY_SPLITW.isArray shouldBe true
|
||||
BaseDataType.UBYTE.isArray shouldBe false
|
||||
}
|
||||
|
||||
test("isPointerArray property") {
|
||||
DataType.arrayOfPointersTo(BaseDataType.UBYTE).isPointerArray shouldBe true
|
||||
DataType.arrayOfPointersTo(BaseDataType.WORD).isPointerArray shouldBe true
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isPointerArray shouldBe false
|
||||
}
|
||||
|
||||
test("isPointer extension") {
|
||||
BaseDataType.POINTER.isPointer.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isPointer.shouldBeFalse()
|
||||
BaseDataType.POINTER.isPointer shouldBe true
|
||||
BaseDataType.UBYTE.isPointer shouldBe false
|
||||
}
|
||||
|
||||
test("isSplitWordArray extension") {
|
||||
BaseDataType.ARRAY_SPLITW.isSplitWordArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_POINTER.isSplitWordArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY.isSplitWordArray.shouldBeFalse()
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe true
|
||||
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe true
|
||||
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe false
|
||||
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe false
|
||||
|
||||
DataType.arrayFor(BaseDataType.UWORD, Cx16Target()).isSplitWordArray(Cx16Target()) shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UWORD, VMTarget()).isSplitWordArray(VMTarget()) shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UWORD, Amiga500Target()).isSplitWordArray(Amiga500Target()) shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -113,20 +134,20 @@ class TestDataType: FunSpec({
|
||||
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
|
||||
}
|
||||
|
||||
test("DataType.forDt throws for struct instance") {
|
||||
shouldThrow<NotImplementedError> {
|
||||
DataType.forDt(BaseDataType.STRUCT_INSTANCE)
|
||||
}
|
||||
test("DataType.forDt supports struct instance") {
|
||||
val dt = DataType.forDt(BaseDataType.STRUCT_INSTANCE)
|
||||
dt.base shouldBe BaseDataType.STRUCT_INSTANCE
|
||||
dt.isStructInstance shouldBe true
|
||||
}
|
||||
|
||||
test("DataType.arrayFor creates array types") {
|
||||
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE)
|
||||
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE, tgt)
|
||||
ubyteArray.base shouldBe BaseDataType.ARRAY
|
||||
ubyteArray.sub shouldBe BaseDataType.UBYTE
|
||||
|
||||
val wordArraySplit = DataType.arrayFor(BaseDataType.WORD)
|
||||
wordArraySplit.base shouldBe BaseDataType.ARRAY_SPLITW
|
||||
wordArraySplit.sub shouldBe BaseDataType.WORD
|
||||
val wordArray = DataType.arrayFor(BaseDataType.WORD, tgt)
|
||||
wordArray.base shouldBe BaseDataType.ARRAY
|
||||
wordArray.sub shouldBe BaseDataType.WORD
|
||||
}
|
||||
|
||||
test("DataType.pointer creates pointer types") {
|
||||
@@ -140,45 +161,45 @@ class TestDataType: FunSpec({
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.isBasic property") {
|
||||
DataType.UBYTE.isBasic.shouldBeTrue()
|
||||
DataType.WORD.isBasic.shouldBeTrue()
|
||||
DataType.STR.isBasic.shouldBeFalse()
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isBasic.shouldBeFalse()
|
||||
DataType.UBYTE.isBasic shouldBe true
|
||||
DataType.WORD.isBasic shouldBe true
|
||||
DataType.STR.isBasic shouldBe false
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isBasic shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isByte property") {
|
||||
DataType.UBYTE.isByte.shouldBeTrue()
|
||||
DataType.BYTE.isByte.shouldBeTrue()
|
||||
DataType.WORD.isByte.shouldBeFalse()
|
||||
DataType.UBYTE.isByte shouldBe true
|
||||
DataType.BYTE.isByte shouldBe true
|
||||
DataType.WORD.isByte shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isNumeric property") {
|
||||
DataType.UBYTE.isNumeric.shouldBeTrue()
|
||||
DataType.FLOAT.isNumeric.shouldBeTrue()
|
||||
DataType.BOOL.isNumeric.shouldBeFalse()
|
||||
DataType.STR.isNumeric.shouldBeFalse()
|
||||
DataType.UBYTE.isNumeric shouldBe true
|
||||
DataType.FLOAT.isNumeric shouldBe true
|
||||
DataType.BOOL.isNumeric shouldBe false
|
||||
DataType.STR.isNumeric shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isArray property") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isArray.shouldBeTrue()
|
||||
DataType.UBYTE.isArray.shouldBeFalse()
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isArray shouldBe true
|
||||
DataType.UBYTE.isArray shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isPointer property") {
|
||||
DataType.pointer(BaseDataType.UBYTE).isPointer.shouldBeTrue()
|
||||
DataType.UBYTE.isPointer.shouldBeFalse()
|
||||
DataType.pointer(BaseDataType.UBYTE).isPointer shouldBe true
|
||||
DataType.UBYTE.isPointer shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isString property") {
|
||||
DataType.STR.isString.shouldBeTrue()
|
||||
DataType.UBYTE.isString.shouldBeFalse()
|
||||
DataType.STR.isString shouldBe true
|
||||
DataType.UBYTE.isString shouldBe false
|
||||
}
|
||||
|
||||
test("DataType.isPassByRef property") {
|
||||
DataType.STR.isPassByRef.shouldBeTrue()
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isPassByRef.shouldBeTrue()
|
||||
DataType.UBYTE.isPassByRef.shouldBeFalse()
|
||||
DataType.pointer(BaseDataType.UBYTE).isPassByRef.shouldBeFalse()
|
||||
DataType.STR.isPassByRef shouldBe true
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).isPassByRef shouldBe true
|
||||
DataType.UBYTE.isPassByRef shouldBe false
|
||||
DataType.pointer(BaseDataType.UBYTE).isPassByRef shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -186,8 +207,8 @@ class TestDataType: FunSpec({
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.elementType returns element type for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).elementType() shouldBe DataType.UBYTE
|
||||
DataType.arrayFor(BaseDataType.WORD).elementType() shouldBe DataType.WORD
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).elementType() shouldBe DataType.UBYTE
|
||||
DataType.arrayFor(BaseDataType.WORD, tgt).elementType() shouldBe DataType.WORD
|
||||
DataType.STR.elementType() shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
@@ -226,16 +247,31 @@ class TestDataType: FunSpec({
|
||||
}
|
||||
|
||||
test("BaseDataType.largerSizeThan") {
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD) shouldBe true
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL) shouldBe true
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER) shouldBe false
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.LONG) shouldBe false
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL) shouldBe true
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD) shouldBe false
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR) shouldBe false
|
||||
}
|
||||
|
||||
test("BaseDataType.equalsSize") {
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE) shouldBe true
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE) shouldBe true
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD) shouldBe false
|
||||
BaseDataType.STR.equalsSize(BaseDataType.STR) shouldBe true
|
||||
BaseDataType.STR.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.STR.equalsSize(BaseDataType.BYTE) shouldBe false
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD) shouldBe true
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.STR) shouldBe false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -243,39 +279,39 @@ class TestDataType: FunSpec({
|
||||
// ============================================================================
|
||||
|
||||
test("bool assignability") {
|
||||
DataType.BOOL.isAssignableTo(DataType.BOOL).shouldBeTrue()
|
||||
DataType.BOOL.isAssignableTo(DataType.UBYTE).shouldBeFalse()
|
||||
DataType.BOOL.isAssignableTo(DataType.BOOL) shouldBe true
|
||||
DataType.BOOL.isAssignableTo(DataType.UBYTE) shouldBe false
|
||||
}
|
||||
|
||||
test("ubyte assignability") {
|
||||
DataType.UBYTE.isAssignableTo(DataType.UBYTE).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.FLOAT).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.UBYTE) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.LONG) shouldBe true
|
||||
DataType.UBYTE.isAssignableTo(DataType.FLOAT) shouldBe true
|
||||
}
|
||||
|
||||
test("byte assignability") {
|
||||
DataType.BYTE.isAssignableTo(DataType.BYTE).shouldBeTrue()
|
||||
DataType.BYTE.isAssignableTo(DataType.WORD).shouldBeTrue()
|
||||
DataType.BYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
DataType.BYTE.isAssignableTo(DataType.BYTE) shouldBe true
|
||||
DataType.BYTE.isAssignableTo(DataType.WORD) shouldBe true
|
||||
DataType.BYTE.isAssignableTo(DataType.LONG) shouldBe true
|
||||
}
|
||||
|
||||
test("uword assignability") {
|
||||
DataType.UWORD.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
DataType.UWORD.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
DataType.UWORD.isAssignableTo(DataType.FLOAT).shouldBeTrue()
|
||||
DataType.UWORD.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
DataType.UWORD.isAssignableTo(DataType.LONG) shouldBe true
|
||||
DataType.UWORD.isAssignableTo(DataType.FLOAT) shouldBe true
|
||||
}
|
||||
|
||||
test("string assignability") {
|
||||
DataType.STR.isAssignableTo(DataType.STR).shouldBeTrue()
|
||||
DataType.STR.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
DataType.STR.isAssignableTo(DataType.STR) shouldBe true
|
||||
DataType.STR.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
}
|
||||
|
||||
test("pointer assignability") {
|
||||
val ptr = DataType.pointer(BaseDataType.UBYTE)
|
||||
ptr.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
ptr.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)).shouldBeTrue()
|
||||
ptr.isAssignableTo(DataType.UWORD) shouldBe true
|
||||
ptr.isAssignableTo(DataType.LONG) shouldBe true
|
||||
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)) shouldBe true
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -290,7 +326,7 @@ class TestDataType: FunSpec({
|
||||
}
|
||||
|
||||
test("DataType.toString for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).toString() shouldBe "ubyte[]"
|
||||
DataType.arrayFor(BaseDataType.WORD).toString() shouldBe "word[] (split)"
|
||||
DataType.arrayFor(BaseDataType.UBYTE, tgt).toString() shouldBe "ubyte[]"
|
||||
DataType.arrayFor(BaseDataType.WORD, tgt).toString() shouldBe "word[]"
|
||||
}
|
||||
})
|
||||
@@ -19,7 +19,7 @@ import prog8.code.target.zp.PETZeropage
|
||||
|
||||
|
||||
/**
|
||||
* Comprehensive tests for Zeropage free list configurations across all targets.
|
||||
* Tests for Zeropage free list configurations across all targets.
|
||||
*
|
||||
* Tests verify that for each target and ZeropageType combination:
|
||||
* - The exact list of free locations is correct
|
||||
@@ -256,16 +256,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have minimal locations") {
|
||||
test("BASICSAFE should have minimal free locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
// After scratch removal, some locations in 0xb1-0xba range should still be free
|
||||
// Just verify the free list is small but non-empty
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
|
||||
zp.free.size shouldBeLessThan 20
|
||||
zp.free.contains(0xfbu) shouldBe true
|
||||
zp.free.contains(0xfcu) shouldBe true
|
||||
zp.free.contains(0xfdu) shouldBe true
|
||||
zp.free.contains(0xfeu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
@@ -279,6 +280,9 @@ class TestZeropageFreeList: FunSpec({
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for PET
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
floatZp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE should exclude IRQ locations") {
|
||||
@@ -628,13 +632,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
|
||||
test("BASICSAFE - extra fb-fe free after scratch removal") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After removing scratch registers from the pool,
|
||||
// only $fb-$fe remain free.
|
||||
zp.free.size shouldBeLessThan 15
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - excludes IRQ locations") {
|
||||
|
||||
@@ -9,5 +9,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,9 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.*
|
||||
import prog8.code.core.ICompilationTarget
|
||||
|
||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||
@@ -13,39 +10,51 @@ import prog8.code.core.ICompilationTarget
|
||||
// TrimmedLine has .value (original), .trimmed (trimStart), and .instruction (instructionPart).
|
||||
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int {
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable) {
|
||||
val pretrimmed = lines.map { it.trimStart() }.toMutableList()
|
||||
|
||||
/** Runs an optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
numberOfOptimizations++
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
while(true) {
|
||||
var modified = false
|
||||
|
||||
/** Runs a single optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
modified = true
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
}
|
||||
return currentLines
|
||||
}
|
||||
return currentLines
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFive = getLinesBy(pretrimmed, lines, 5)
|
||||
linesByFive = runPass(optimizeFoldIndexOffset(linesByFive, symbolTable), 5, linesByFive)
|
||||
|
||||
var linesBySix = getLinesBy(pretrimmed, lines, 6)
|
||||
linesBySix = runPass(optimizeFoldIndexOffsetSplitWord(linesBySix, symbolTable), 6, linesBySix)
|
||||
|
||||
var linesBySeven = getLinesBy(pretrimmed, lines, 7)
|
||||
linesBySeven = runPass(optimizeFoldIndexOffsetNonSplitWord(linesBySeven, symbolTable), 7, linesBySeven)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
if(!modified)
|
||||
break
|
||||
}
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
internal fun String.isBranch() = this.startsWith("b")
|
||||
@@ -85,7 +94,7 @@ private fun apply(modifications: List<Modification>, lines: MutableList<String>,
|
||||
lines.removeAt(modification.lineIndex)
|
||||
else {
|
||||
val pretrim = pretrimmed.getOrNull(modification.lineIndex)
|
||||
if (pretrim == null || pretrim.isBlank() || pretrim[0] == ';')
|
||||
if (pretrim.isNullOrBlank() || pretrim[0] == ';')
|
||||
lines.removeAt(modification.lineIndex)
|
||||
else if (haslabelPretrimmed(pretrim)) {
|
||||
val label = keeplabelPretrimmed(pretrim)
|
||||
@@ -166,6 +175,18 @@ internal fun String.hasInstr(mnemonic: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun replaceMnemonic(raw: String, oldMnemonic: String, newMnemonic: String): String {
|
||||
val trimmed = raw.trimStart()
|
||||
val instruction = trimmed.instructionPart()
|
||||
if(!instruction.startsWith(oldMnemonic))
|
||||
return raw.replaceFirst(oldMnemonic, newMnemonic)
|
||||
val instrStartInTrimmed = trimmed.indexOf(instruction)
|
||||
if(instrStartInTrimmed<0) return raw.replaceFirst(oldMnemonic, newMnemonic)
|
||||
val leadingLen = raw.length - trimmed.length
|
||||
val idxInRaw = leadingLen + instrStartInTrimmed
|
||||
return raw.substring(0, idxInRaw) + newMnemonic + raw.substring(idxInRaw + oldMnemonic.length)
|
||||
}
|
||||
|
||||
/** Extracts the instruction part of a trimmed assembly line, skipping any label prefix.
|
||||
* For "mylabel: lda #1" returns "lda #1". For "lda #1" returns "lda #1". */
|
||||
private fun String.instructionPart(): String {
|
||||
@@ -391,7 +412,95 @@ private fun optimizeSameAssignments(
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSamePointerIndexingAndUselessBeq(linesByFourteen: Sequence<List<TrimmedLine>>): List<Modification> {
|
||||
private const val NO_MEMORY_WRITE = ""
|
||||
private const val UNKNOWN_WRITE_TARGET = "\u0000"
|
||||
|
||||
/** Mnemonics that can change memory (direct stores plus read-modify-write). */
|
||||
private val memoryWritingMnemonics = setOf(
|
||||
"sta", "stx", "sty", "stz", "inc", "dec", "asl", "lsr", "rol", "ror",
|
||||
"and", "ora", "eor", "adc", "sbc", "bit"
|
||||
)
|
||||
|
||||
/** Control transfers that can reach the code after them with a different Y, or change it. */
|
||||
private val flowMnemonics = setOf(
|
||||
"jsr", "rts", "rti", "jmp", "bra", "brk",
|
||||
"bcc", "bcs", "beq", "bmi", "bne", "bpl", "bvc", "bvs", "bbs", "bbr"
|
||||
)
|
||||
|
||||
private fun TrimmedLine.mnemonic(): String =
|
||||
instruction.trimStart().takeWhile { !it.isWhitespace() }.lowercase()
|
||||
|
||||
/** True if the line carries a label, with or without an instruction following it. */
|
||||
private fun TrimmedLine.hasLabel(): Boolean {
|
||||
return haslabelPretrimmed(trimmed) || trimmed.isNotEmpty() && (trimmed[0] == '+' || trimmed[0] == '-')
|
||||
}
|
||||
|
||||
/** The memory operand this instruction writes, with any +offset and ,index suffix stripped.
|
||||
* Returns [NO_MEMORY_WRITE] for immediate and implied forms, and [UNKNOWN_WRITE_TARGET] for
|
||||
* indirect forms, where the destination cannot be determined statically. */
|
||||
private fun TrimmedLine.memoryWriteBase(): String {
|
||||
val op = instruction.extractOperandTrimmed()
|
||||
if (op.isEmpty() || op.startsWith("#") || op == "a") return NO_MEMORY_WRITE
|
||||
if (op.startsWith("(")) return UNKNOWN_WRITE_TARGET
|
||||
return op.removeSuffix(",y").removeSuffix(",x").substringBefore('+')
|
||||
}
|
||||
|
||||
/** True if this line may write to [base]. Conservative: an indirect store is assumed to. */
|
||||
private fun TrimmedLine.mayWriteBase(base: String): Boolean {
|
||||
if (mnemonic() !in memoryWritingMnemonics) return false
|
||||
val target = memoryWriteBase()
|
||||
if (target == NO_MEMORY_WRITE) return false
|
||||
if (target == UNKNOWN_WRITE_TARGET) return true
|
||||
return target.equals(base, ignoreCase = true)
|
||||
}
|
||||
|
||||
/** True if operand is safe to read more than once: an immediate, or memory that is not an
|
||||
* IO location (an IO read can return a different value without the CPU having written it). */
|
||||
private fun isNonVolatileIndexOperand(indexLine: TrimmedLine, machine: ICompilationTarget, symbolTable: SymbolTable): Boolean {
|
||||
val operand = indexLine.instruction.extractOperandTrimmed()
|
||||
if (operand.isEmpty() || operand.startsWith("#")) return true
|
||||
val addr = getAddressArg(indexLine.instruction, symbolTable)
|
||||
return if (addr == null) !looksLikeIOAddress(operand, machine)
|
||||
else !machine.isIOAddress(addr)
|
||||
}
|
||||
|
||||
/** Decides whether the index reload at [reloadIdx] inside [window] can be deleted.
|
||||
*
|
||||
* The reload is redundant only if Y still holds the value it would have loaded. That requires
|
||||
* the reload to load the very same operand as the original index load, and none of the
|
||||
* following in between:
|
||||
* - modifies Y
|
||||
* - writes the index operand itself (so `ldy i / lda arr,y / inc i / ldy i / sta arr,y` keeps
|
||||
* its reload: without it the store lands one element earlier)
|
||||
* - is a call or control transfer, since a callee can clobber Y and another path can arrive
|
||||
* with a different Y
|
||||
* - carries a label, for the same reason
|
||||
* The index operand must also not be a volatile IO location.
|
||||
*/
|
||||
private fun canDropIndexReload(
|
||||
window: List<TrimmedLine>,
|
||||
reloadIdx: Int,
|
||||
indexLine: TrimmedLine,
|
||||
machine: ICompilationTarget,
|
||||
symbolTable: SymbolTable
|
||||
): Boolean {
|
||||
if (reloadIdx <= 1) return false
|
||||
val operand = indexLine.instruction.extractOperandTrimmed()
|
||||
// the reload has to load the same index, or Y holds a different one and is not redundant
|
||||
if (window[reloadIdx].instruction.extractOperandTrimmed() != operand) return false
|
||||
val base = if (operand.startsWith("#") || operand.isEmpty()) NO_MEMORY_WRITE
|
||||
else operand.removeSuffix(",y").removeSuffix(",x").substringBefore('+')
|
||||
for (idx in 1 until reloadIdx) {
|
||||
val line = window[idx]
|
||||
if (line.instruction.modifiesYRegister()) return false
|
||||
if (line.mnemonic() in flowMnemonics) return false
|
||||
if (line.hasLabel()) return false
|
||||
if (base != NO_MEMORY_WRITE && line.mayWriteBase(base)) return false
|
||||
}
|
||||
return isNonVolatileIndexOperand(indexLine, machine, symbolTable)
|
||||
}
|
||||
|
||||
private fun optimizeSamePointerIndexingAndUselessBeq(linesByFourteen: Sequence<List<TrimmedLine>>, machine: ICompilationTarget, symbolTable: SymbolTable): List<Modification> {
|
||||
|
||||
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
||||
// if Y isn't modified in between we can omit the second LDY:
|
||||
@@ -405,30 +514,48 @@ private fun optimizeSamePointerIndexingAndUselessBeq(linesByFourteen: Sequence<L
|
||||
for (lines in linesByFourteen) {
|
||||
val f1 = lines[0].instruction
|
||||
val f2 = lines[1].instruction
|
||||
val f3 = lines[2].instruction
|
||||
//val f3 = lines[2].instruction
|
||||
val f4 = lines[3].instruction
|
||||
val f5 = lines[4].instruction
|
||||
val f6 = lines[5].instruction
|
||||
|
||||
if(f1.startsWith("ldy ") && f2.startsWith("lda ") && f4.startsWith("ldy ") && f5.startsWith("sta ")) {
|
||||
val firstvalue = f1.extractOperandTrimmed()
|
||||
//val firstvalue = f1.extractOperandTrimmed()
|
||||
val secondvalue = f2.extractOperandTrimmed()
|
||||
val fourthvalue = f4.extractOperandTrimmed()
|
||||
//val fourthvalue = f4.extractOperandTrimmed()
|
||||
val fifthvalue = f5.extractOperandTrimmed()
|
||||
if(!f3.modifiesYRegister() && firstvalue==fourthvalue && secondvalue==fifthvalue && secondvalue.endsWith(",y") && fifthvalue.endsWith(",y")) {
|
||||
if(secondvalue==fifthvalue && secondvalue.endsWith(",y") && fifthvalue.endsWith(",y") &&
|
||||
canDropIndexReload(lines, 3, lines[0], machine, symbolTable)) {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
if(f1.startsWith("ldy ") && f2.startsWith("lda ") && f5.startsWith("ldy ") && f6.startsWith("sta ")) {
|
||||
val firstvalue = f1.extractOperandTrimmed()
|
||||
val secondvalue = f2.extractOperandTrimmed()
|
||||
val fifthvalue = f5.extractOperandTrimmed()
|
||||
val sixthvalue = f6.extractOperandTrimmed()
|
||||
if(!f3.modifiesYRegister() && !f4.modifiesYRegister() && firstvalue==fifthvalue && secondvalue==sixthvalue && secondvalue.endsWith(",y") && sixthvalue.endsWith(",y")) {
|
||||
if(secondvalue==sixthvalue && secondvalue.endsWith(",y") && sixthvalue.endsWith(",y") &&
|
||||
canDropIndexReload(lines, 4, lines[0], machine, symbolTable)) {
|
||||
mods.add(Modification(lines[4].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
// Relaxed variant: two consecutive indexed accesses, not necessarily a matched load/store
|
||||
// pair. Only the index reload has to be the same, and nothing in between may disturb it.
|
||||
// ldy V
|
||||
// lda A,y
|
||||
// ... <-- does not modify Y, does not write V, no call or label
|
||||
// ldy V <-- can be removed
|
||||
// lda B,y
|
||||
if(f1.startsWith("ldy ") && f2.startsWith("lda ") && f4.startsWith("ldy ") && f5.startsWith("lda ") &&
|
||||
f2.endsWith(",y") && f5.endsWith(",y") &&
|
||||
canDropIndexReload(lines, 3, lines[0], machine, symbolTable)) {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
if(f1.startsWith("ldy ") && f2.startsWith("lda ") && f5.startsWith("ldy ") && f6.startsWith("lda ") &&
|
||||
f2.endsWith(",y") && f6.endsWith(",y") &&
|
||||
canDropIndexReload(lines, 4, lines[0], machine, symbolTable)) {
|
||||
mods.add(Modification(lines[4].index, true, null))
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
beq +
|
||||
@@ -616,11 +743,9 @@ private fun looksLikeIOAddress(operand: String, machine: ICompilationTarget): Bo
|
||||
}
|
||||
// Try to parse decimal address (e.g. "53280" = $d000) and delegate to machine
|
||||
val decVal = operand.toUIntOrNull()
|
||||
if(decVal != null)
|
||||
return machine.isIOAddress(decVal)
|
||||
// For unresolved symbolic names we can't determine - assume NOT IO
|
||||
// (the symbol table lookup already failed, so we have no better info)
|
||||
return false
|
||||
return decVal != null && machine.isIOAddress(decVal)
|
||||
}
|
||||
|
||||
private fun optimizeIncDec(linesByFour: Sequence<List<TrimmedLine>>): List<Modification> {
|
||||
@@ -670,7 +795,8 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
|
||||
}
|
||||
else if (tfirst.hasInstr("jsr") && tsecond.hasInstr("rts")) {
|
||||
if(!tfirst.contains("floats.pushFAC") && !tfirst.contains("floats.popFAC")) { // these 2 routines depend on being called with JSR!!
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
val replacement = replaceMnemonic(lines[0].value, "jsr", "jmp")
|
||||
mods += Modification(lines[0].index, false, replacement)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
}
|
||||
@@ -747,7 +873,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
|
||||
val firstMnemonic = tfirst.take(3)
|
||||
branchInversions[firstMnemonic]?.let { invertedBranch ->
|
||||
if (sameLabel(tfirst, tsecond, tthird)) {
|
||||
val branch = lines[1].value.replace("jmp", invertedBranch)
|
||||
val branch = replaceMnemonic(lines[1].value, "jmp", invertedBranch)
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
@@ -873,6 +999,205 @@ private fun optimizeUnneededTempvarInAdd(linesByFour: Sequence<List<TrimmedLine>
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun withLabel(line: TrimmedLine, newInstruction: String): String {
|
||||
val label = keeplabelPretrimmed(line.value)
|
||||
return if (label.isEmpty()) newInstruction else "$label ${newInstruction.trimStart()}"
|
||||
}
|
||||
|
||||
private fun scaleConstant(constStr: String, scale: Int): String {
|
||||
val value = if (constStr.startsWith("$"))
|
||||
constStr.substring(1).toInt(16)
|
||||
else
|
||||
constStr.toInt()
|
||||
val scaled = value * scale
|
||||
return if (constStr.startsWith("$"))
|
||||
"$${scaled.toString(16).padStart(2, '0')}"
|
||||
else
|
||||
scaled.toString()
|
||||
}
|
||||
|
||||
private fun isArrayByteSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
|
||||
return when (val symbol = symbolTable.flat[name]) {
|
||||
is StStaticVariable -> {
|
||||
val len = symbol.length
|
||||
(symbol.dt.isByteArray || symbol.dt.isString) && len != null && len <= 256u
|
||||
}
|
||||
is StMemVar -> {
|
||||
val len = symbol.length
|
||||
symbol.dt.isByteArray && len != null && len <= 256u
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isArrayWordSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
|
||||
return when (val symbol = symbolTable.flat[name]) {
|
||||
is StStaticVariable -> {
|
||||
val len = symbol.length
|
||||
symbol.dt.isWordArray && len != null && len <= 256u
|
||||
}
|
||||
is StMemVar -> {
|
||||
val len = symbol.length
|
||||
symbol.dt.isWordArray && len != null && len <= 256u
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeFoldIndexOffset(
|
||||
linesByFive: Sequence<List<TrimmedLine>>,
|
||||
symbolTable: SymbolTable
|
||||
): List<Modification> {
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for (lines in linesByFive) {
|
||||
val first = lines[0].instruction
|
||||
val second = lines[1].instruction
|
||||
val third = lines[2].instruction
|
||||
val fourth = lines[3].instruction
|
||||
val fifth = lines[4].instruction
|
||||
|
||||
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
|
||||
continue
|
||||
if (fourth != "tay" && fourth != "tax")
|
||||
continue
|
||||
|
||||
val reg = fourth[2]
|
||||
val constOperand = third.extractOperandTrimmed()
|
||||
if (!constOperand.startsWith("#"))
|
||||
continue
|
||||
val constValue = constOperand.substring(1)
|
||||
|
||||
val fifthOperand = fifth.extractOperandTrimmed()
|
||||
if (!fifthOperand.endsWith(",$reg") || fifthOperand.startsWith("("))
|
||||
continue
|
||||
|
||||
val addrPart = fifthOperand.dropLast(2)
|
||||
if (!isArrayByteSizedAndSmall(addrPart, symbolTable)) continue
|
||||
|
||||
val mnemonic = fifth.take(3)
|
||||
val varName = first.extractOperandTrimmed()
|
||||
mods.add(Modification(lines[0].index, false, withLabel(lines[0], " ld$reg $varName")))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[4].index, false, withLabel(lines[4], " $mnemonic $addrPart+$constValue,$reg")))
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeFoldIndexOffsetSplitWord(
|
||||
linesBySix: Sequence<List<TrimmedLine>>,
|
||||
symbolTable: SymbolTable
|
||||
): List<Modification> {
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for (lines in linesBySix) {
|
||||
val first = lines[0].instruction
|
||||
val second = lines[1].instruction
|
||||
val third = lines[2].instruction
|
||||
val fourth = lines[3].instruction
|
||||
val fifth = lines[4].instruction
|
||||
val sixth = lines[5].instruction
|
||||
|
||||
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
|
||||
continue
|
||||
if (fourth != "tay" && fourth != "tax")
|
||||
continue
|
||||
|
||||
val reg = fourth[2]
|
||||
val constOperand = third.extractOperandTrimmed()
|
||||
if (!constOperand.startsWith("#"))
|
||||
continue
|
||||
val constValue = constOperand.substring(1)
|
||||
|
||||
val fifthOp = fifth.extractOperandTrimmed()
|
||||
val sixthOp = sixth.extractOperandTrimmed()
|
||||
if (!fifthOp.endsWith(",$reg") || fifthOp.startsWith("("))
|
||||
continue
|
||||
if (!sixthOp.endsWith(",$reg") || sixthOp.startsWith("("))
|
||||
continue
|
||||
|
||||
val lsbAddr = fifthOp.dropLast(2)
|
||||
val msbAddr = sixthOp.dropLast(2)
|
||||
if (!lsbAddr.endsWith("_lsb") || !msbAddr.endsWith("_msb"))
|
||||
continue
|
||||
val baseName = lsbAddr.dropLast(4)
|
||||
if (msbAddr != baseName + "_msb")
|
||||
continue
|
||||
|
||||
if (!isArrayWordSizedAndSmall(baseName, symbolTable))
|
||||
continue
|
||||
|
||||
val lsbMnemonic = fifth.take(3)
|
||||
val msbMnemonic = sixth.take(3)
|
||||
val varName = first.extractOperandTrimmed()
|
||||
|
||||
mods.add(Modification(lines[0].index, false, withLabel(lines[0], " ld$reg $varName")))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[4].index, false, withLabel(lines[4], " $lsbMnemonic $lsbAddr+$constValue,$reg")))
|
||||
mods.add(Modification(lines[5].index, false, withLabel(lines[5], " $msbMnemonic $msbAddr+$constValue,$reg")))
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeFoldIndexOffsetNonSplitWord(
|
||||
linesBySeven: Sequence<List<TrimmedLine>>,
|
||||
symbolTable: SymbolTable
|
||||
): List<Modification> {
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for (lines in linesBySeven) {
|
||||
val first = lines[0].instruction
|
||||
val second = lines[1].instruction
|
||||
val third = lines[2].instruction
|
||||
val fourth = lines[3].instruction
|
||||
val fifth = lines[4].instruction
|
||||
val sixth = lines[5].instruction
|
||||
val seventh = lines[6].instruction
|
||||
|
||||
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
|
||||
continue
|
||||
if (fourth != "asl a" || (fifth != "tay" && fifth != "tax"))
|
||||
continue
|
||||
|
||||
val reg = fifth[2]
|
||||
val constOperand = third.extractOperandTrimmed()
|
||||
if (!constOperand.startsWith("#"))
|
||||
continue
|
||||
val constValue = constOperand.substring(1)
|
||||
val scaledConst = scaleConstant(constValue, 2)
|
||||
|
||||
val sixthOp = sixth.extractOperandTrimmed()
|
||||
val seventhOp = seventh.extractOperandTrimmed()
|
||||
if (!sixthOp.endsWith(",$reg") || sixthOp.startsWith("("))
|
||||
continue
|
||||
if (!seventhOp.endsWith(",$reg") || seventhOp.startsWith("("))
|
||||
continue
|
||||
|
||||
val baseAddr = sixthOp.dropLast(2)
|
||||
if (seventhOp != "$baseAddr+1,$reg")
|
||||
continue
|
||||
|
||||
if (!isArrayWordSizedAndSmall(baseAddr, symbolTable))
|
||||
continue
|
||||
|
||||
val lowMnemonic = sixth.take(3)
|
||||
val highMnemonic = seventh.take(3)
|
||||
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[5].index, false, withLabel(lines[5], " $lowMnemonic $baseAddr+$scaledConst,$reg")))
|
||||
mods.add(Modification(lines[6].index, false, withLabel(lines[6], " $highMnemonic $baseAddr+1+$scaledConst,$reg")))
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen: Sequence<List<TrimmedLine>>): List<Modification> {
|
||||
/*
|
||||
; FIRST SEQUYENCE: P8ZP_SCRATCH_PTR += AY :
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSubroutineParameter
|
||||
import prog8.code.core.CombinedLongRegisters
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) combined cx16 virtual registers (longs),
|
||||
@@ -15,6 +17,8 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// 4) single CPU registers (order Y,X,A),
|
||||
// 5) floating point registers (FAC1, FAC2),
|
||||
// 6) CPU Carry status flag
|
||||
// Within each group, complex expressions are evaluated first (they may clobber registers,
|
||||
// so running them before simple expressions avoids needing to save/restore already-loaded values).
|
||||
val args = sub.parameters.withIndex()
|
||||
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
|
||||
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
@@ -23,12 +27,17 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
combinedLongRegs.forEach { order += it.index }
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
singleCpuRegs
|
||||
.sortedWith(
|
||||
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
|
||||
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
|
||||
)
|
||||
.forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
floatRegs.forEach { order += it.index }
|
||||
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
rest.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,7 @@ import prog8.code.StMemVar
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.PtForLoop
|
||||
import prog8.code.ast.PtIdentifier
|
||||
import prog8.code.ast.PtNumber
|
||||
import prog8.code.ast.PtRange
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
@@ -33,21 +34,502 @@ internal class ForLoopsAsmGen(
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
if(range.step.asConstInteger()!! < -1) {
|
||||
val constStep = range.step.asConstInteger()
|
||||
if(constStep != null && constStep < -1) {
|
||||
val limit = range.to.asConstInteger()
|
||||
if(limit==0)
|
||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||
}
|
||||
|
||||
if(range.step !is PtNumber) {
|
||||
translateForOverNonconstStepRange(stmt, iterableDt, range)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstStepRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstStepByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstStepWordRange(stmt, iterableDt, range)
|
||||
iterableDt.isLongArray -> throw AssemblyError("variable-step for loops over long ranges are not supported by the legacy 6502 code generator at ${stmt.position}")
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun forOverNonconstStepByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val loopDt = iterableDt.elementType()
|
||||
val loopvar = asmgen.asmVariableName(stmt.variable)
|
||||
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
|
||||
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
|
||||
// Keep range expression evaluation in source order and preserve each value across the next one.
|
||||
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
|
||||
if (range.step.type.isSigned)
|
||||
asmgen.out(" lda $stepVar | beq $endLabel | bmi $descendingLabel")
|
||||
else
|
||||
asmgen.out(" lda $stepVar | beq $endLabel")
|
||||
|
||||
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
emitByteRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
|
||||
|
||||
if(descendingLabel != null) {
|
||||
asmgen.out(descendingLabel)
|
||||
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
|
||||
asmgen.out(" jmp $loopLabel")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun emitByteRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
|
||||
val notEqualLabel = asmgen.makeLabel("for_not_equal")
|
||||
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notEqualLabel
|
||||
jmp $loopLabel
|
||||
$notEqualLabel
|
||||
sec
|
||||
lda $loopvar
|
||||
sbc $toValueVar
|
||||
bvc $compareDoneLabel
|
||||
eor #$80
|
||||
$compareDoneLabel
|
||||
${if (ascending) "bpl" else "bmi"} $endLabel
|
||||
""")
|
||||
} else if (ascending) {
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
cmp $loopvar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitByteRangeUpdate(
|
||||
loopvar: String,
|
||||
toValueVar: String,
|
||||
stepVar: String,
|
||||
nextVar: String,
|
||||
endLabel: String,
|
||||
loopLabel: String,
|
||||
loopvarSigned: Boolean,
|
||||
stepSigned: Boolean
|
||||
) {
|
||||
val notLastLabel = asmgen.makeLabel("for_not_last")
|
||||
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
|
||||
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
|
||||
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
|
||||
val storeNextLabel = asmgen.makeLabel("for_store_next")
|
||||
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
|
||||
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
|
||||
asmgen.out("""
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notLastLabel
|
||||
jmp $endLabel
|
||||
$notLastLabel
|
||||
""")
|
||||
if (stepSigned)
|
||||
asmgen.out(" lda $stepVar | bmi $descendingUpdateLabel")
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
$ascendingWrapBranch $endLabel
|
||||
sta $nextVar
|
||||
""")
|
||||
asmgen.out("""
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
beq $storeNextLabel
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
bvc $ascendingCompareDoneLabel
|
||||
eor #$80
|
||||
$ascendingCompareDoneLabel
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out(" bpl $endLabel")
|
||||
else
|
||||
asmgen.out(" bcs $endLabel")
|
||||
if(stepSigned) {
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$descendingUpdateLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
$descendingWrapBranch $endLabel
|
||||
sta $nextVar
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
bvc $descendingCompareDoneLabel
|
||||
eor #$80
|
||||
$descendingCompareDoneLabel
|
||||
bmi $endLabel
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$storeNextLabel
|
||||
lda $nextVar
|
||||
sta $loopvar
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
|
||||
private fun forOverNonconstStepWordRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val loopDt = iterableDt.elementType()
|
||||
val loopvar = asmgen.asmVariableName(stmt.variable)
|
||||
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
|
||||
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
|
||||
// Keep range expression evaluation in source order and preserve each value across the next one.
|
||||
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
|
||||
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
|
||||
if (range.step.type.isSigned)
|
||||
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel | lda ${stepVar}+1 | bmi $descendingLabel")
|
||||
else
|
||||
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel")
|
||||
|
||||
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
emitWordRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
|
||||
|
||||
if(descendingLabel != null) {
|
||||
asmgen.out(descendingLabel)
|
||||
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
|
||||
asmgen.out(" jmp $loopLabel")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun emitWordRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
|
||||
val notEqualLabel = asmgen.makeLabel("for_not_equal")
|
||||
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $notEqualLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bne $notEqualLabel
|
||||
jmp $loopLabel
|
||||
$notEqualLabel
|
||||
sec
|
||||
lda $loopvar
|
||||
sbc $toValueVar
|
||||
lda $loopvar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $compareDoneLabel
|
||||
eor #$80
|
||||
$compareDoneLabel
|
||||
${if (ascending) "bpl" else "bmi"} $endLabel
|
||||
""")
|
||||
} else if (ascending) {
|
||||
asmgen.out("""
|
||||
lda $toValueVar+1
|
||||
cmp $loopvar+1
|
||||
bcc $endLabel
|
||||
bne $loopLabel
|
||||
lda $toValueVar
|
||||
cmp $loopvar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bcc $endLabel
|
||||
bne $loopLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitWordRangeUpdate(
|
||||
loopvar: String,
|
||||
toValueVar: String,
|
||||
stepVar: String,
|
||||
nextVar: String,
|
||||
endLabel: String,
|
||||
loopLabel: String,
|
||||
loopvarSigned: Boolean,
|
||||
stepSigned: Boolean
|
||||
) {
|
||||
val notLastLabel = asmgen.makeLabel("for_not_last")
|
||||
val ascendingCompareLabel = asmgen.makeLabel("for_next_compare")
|
||||
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
|
||||
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
|
||||
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
|
||||
val descendingNotLastLabel = if(stepSigned) asmgen.makeLabel("for_desc_not_last") else null
|
||||
val storeNextLabel = asmgen.makeLabel("for_store_next")
|
||||
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
|
||||
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
|
||||
asmgen.out("""
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $notLastLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
$notLastLabel
|
||||
""")
|
||||
if (stepSigned)
|
||||
asmgen.out(" lda ${stepVar}+1 | bmi $descendingUpdateLabel")
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
sta $nextVar
|
||||
lda $loopvar+1
|
||||
adc $stepVar+1
|
||||
$ascendingWrapBranch $endLabel
|
||||
sta $nextVar+1
|
||||
""")
|
||||
asmgen.out("""
|
||||
lda $nextVar+1
|
||||
cmp $toValueVar+1
|
||||
bne $ascendingCompareLabel
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
beq $storeNextLabel
|
||||
$ascendingCompareLabel
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
lda $nextVar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $ascendingCompareDoneLabel
|
||||
eor #$80
|
||||
$ascendingCompareDoneLabel
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out(" bpl $endLabel")
|
||||
else
|
||||
asmgen.out(" bcs $endLabel")
|
||||
if(stepSigned) {
|
||||
asmgen.out("""
|
||||
jmp $storeNextLabel
|
||||
|
||||
$descendingUpdateLabel
|
||||
lda $loopvar+1
|
||||
cmp $toValueVar+1
|
||||
bne $descendingNotLastLabel
|
||||
lda $loopvar
|
||||
cmp $toValueVar
|
||||
beq $endLabel
|
||||
$descendingNotLastLabel
|
||||
clc
|
||||
lda $loopvar
|
||||
adc $stepVar
|
||||
sta $nextVar
|
||||
lda $loopvar+1
|
||||
adc $stepVar+1
|
||||
$descendingWrapBranch $endLabel
|
||||
sta $nextVar+1
|
||||
""")
|
||||
if (loopvarSigned)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $nextVar
|
||||
sbc $toValueVar
|
||||
lda $nextVar+1
|
||||
sbc $toValueVar+1
|
||||
bvc $descendingCompareDoneLabel
|
||||
eor #$80
|
||||
$descendingCompareDoneLabel
|
||||
bmi $endLabel
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $nextVar+1
|
||||
cmp $toValueVar+1
|
||||
bcc $endLabel
|
||||
bne $storeNextLabel
|
||||
lda $nextVar
|
||||
cmp $toValueVar
|
||||
bcc $endLabel
|
||||
""")
|
||||
}
|
||||
else asmgen.out(" jmp $storeNextLabel")
|
||||
asmgen.out("""
|
||||
$storeNextLabel
|
||||
lda $nextVar
|
||||
sta $loopvar
|
||||
lda $nextVar+1
|
||||
sta $loopvar+1
|
||||
jmp $loopLabel
|
||||
""")
|
||||
}
|
||||
|
||||
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
|
||||
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1 || stepsize == -1)
|
||||
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
else
|
||||
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
|
||||
// Use temporary variable to store the limit
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1) {
|
||||
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
|
||||
asmgen.out("""
|
||||
inc $toValueVar
|
||||
bne +
|
||||
inc $toValueVar+1
|
||||
bne +
|
||||
inc $toValueVar+2
|
||||
bne +
|
||||
inc $toValueVar+3
|
||||
+
|
||||
""")
|
||||
} else {
|
||||
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
|
||||
val label1 = asmgen.makeLabel("dec_1")
|
||||
val label2 = asmgen.makeLabel("dec_2")
|
||||
val label3 = asmgen.makeLabel("dec_3")
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
bne $label1
|
||||
lda $toValueVar+1
|
||||
bne $label2
|
||||
lda $toValueVar+2
|
||||
bne $label3
|
||||
dec $toValueVar+3
|
||||
$label3: dec $toValueVar+2
|
||||
$label2: dec $toValueVar+1
|
||||
$label1: dec $toValueVar
|
||||
""")
|
||||
}
|
||||
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
|
||||
if (stepsize == 1) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
TODO("legacy 6502 codegen does not support long FOR ranges with step other than 1 or -1 at ${forloop.position}")
|
||||
}
|
||||
|
||||
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
@@ -705,7 +1187,7 @@ $loopLabel sty $indexVar
|
||||
when {
|
||||
iterableDt.isString -> iterateStrings()
|
||||
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
|
||||
iterableDt.isSplitWordArray -> iterateSplitWords()
|
||||
iterableDt.isSplitWordArray(asmgen.options.compTarget) -> iterateSplitWords()
|
||||
iterableDt.isWordArray -> iterateWords()
|
||||
iterableDt.isLongArray -> iterateWords(true)
|
||||
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
|
||||
@@ -803,7 +1285,7 @@ $loopLabel""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> {
|
||||
// loop over word range via loopvar, step >= 2 or <= -2
|
||||
when (range.step) {
|
||||
1, -1 -> {
|
||||
@@ -974,16 +1456,15 @@ $loopLabel""")
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
inc $varname
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
inc $varname+1
|
||||
+ lda $varname
|
||||
cmp #<${range.last+1}
|
||||
bne $loopLabel
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
lda $varname+1
|
||||
cmp #>${range.last+1}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
@@ -1004,24 +1485,21 @@ $loopLabel""")
|
||||
if(range.last==0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++
|
||||
lda $varname+1
|
||||
beq $endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp #<${range.last-1}
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp #>${range.last-1}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
@@ -1033,7 +1511,7 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
val toPlus1 = (range.last + 1).toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
@@ -1046,26 +1524,25 @@ $loopLabel""")
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
inc $varname
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
bne $loopLabel
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp #$${toPlus1.substring(6, 8)}
|
||||
bne $loopLabel
|
||||
inc $varname+3""")
|
||||
asmgen.jmp(loopLabel)
|
||||
lda $varname+1
|
||||
cmp #$${toPlus1.substring(4, 6)}
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp #$${toPlus1.substring(2, 4)}
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp #$${toPlus1.take(2)}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
@@ -1077,7 +1554,7 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
val toMinus1 = (range.last - 1).toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
@@ -1099,20 +1576,6 @@ $loopLabel""")
|
||||
bne ++
|
||||
lda $varname+3
|
||||
beq $endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
@@ -1124,8 +1587,19 @@ $loopLabel""")
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp #$${toMinus1.substring(6, 8)}
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp #$${toMinus1.substring(4, 6)}
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp #$${toMinus1.substring(2, 4)}
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp #$${toMinus1.take(2)}
|
||||
bne $loopLabel""")
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
|
||||
|
||||
/*
|
||||
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
|
||||
* They are NOT called with jsr - their assembly body is copied directly into the caller.
|
||||
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
|
||||
* CPU register (A/X/Y) and the caller picks it up.
|
||||
*
|
||||
* This is handled at line 68: `if (sub.inline) { ... }`.
|
||||
* Any new codegen backend MUST replicate this behavior.
|
||||
*/
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)!!
|
||||
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
|
||||
if(symbol.type == StNodeType.LABEL) {
|
||||
require(call.void)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
|
||||
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
val varBank = sub.address?.varbank
|
||||
var varbank: String? = null
|
||||
var bankFixLabel: String? = null
|
||||
if (varBank != null) {
|
||||
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
|
||||
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
|
||||
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
|
||||
val extsubId = asmgen.extsubCallSiteIds[sub]
|
||||
if (extsubId != null) {
|
||||
asmgen.out(" lda #${extsubId.toInt()}")
|
||||
}
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
|
||||
// store bank byte directly into the JSRFAR instruction data (forward label reference)
|
||||
bankFixLabel = asmgen.makeLabel("bank")
|
||||
asmgen.out(" sta $bankFixLabel")
|
||||
} else {
|
||||
varbank = asmgen.asmVariableName(varBank)
|
||||
}
|
||||
}
|
||||
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline) {
|
||||
// inline the subroutine. (regardless of optimization settings!)
|
||||
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
} else {
|
||||
val bank = sub.address?.constbank?.toString()
|
||||
if(bank==null) {
|
||||
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
|
||||
if(varbank!=null) {
|
||||
if(varbank!=null || bankFixLabel!=null) {
|
||||
if(asmgen.options.romable)
|
||||
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
|
||||
|
||||
// variable bank case: save/restore registers around the self-modifying sta
|
||||
if(varbank!=null) {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
val label = bankFixLabel ?: "+"
|
||||
|
||||
// self-modifying code: set jsrfar bank argument
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
@@ -343,8 +387,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
|
||||
}
|
||||
val src = if(value.type.isPassByRef) {
|
||||
if(value is PtIdentifier) {
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
|
||||
if(value is PtIdentifier || value is PtPointerDeref) {
|
||||
val addr = PtAddressOf(value.type.typeForUntypedAddressOf(false, program.memsizer), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = scope as PtNode
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
@@ -382,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(argType.isUnsignedWord && paramType.isString)
|
||||
return true
|
||||
|
||||
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +313,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
|
||||
val left = condition.left
|
||||
val right = condition.right
|
||||
val operator = condition.operator
|
||||
|
||||
when (operator) {
|
||||
when (val operator = condition.operator) {
|
||||
"==" -> {
|
||||
// if X==value
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, signed)
|
||||
@@ -595,9 +593,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
|
||||
val left = condition.left
|
||||
val right = condition.right
|
||||
val operator = condition.operator
|
||||
|
||||
return when (operator) {
|
||||
return when (val operator = condition.operator) {
|
||||
"==" -> longEqualsValue(left, right, false, jumpAfterIf, stmt)
|
||||
"!=" -> longEqualsValue(left, right, true, jumpAfterIf, stmt)
|
||||
"<", "<=", ">", ">=" -> compareLongValues(left, operator, right, jumpAfterIf, stmt)
|
||||
@@ -932,19 +928,17 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" lda $varname+$offset")
|
||||
return
|
||||
}
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
when (e) {
|
||||
when (val e = asmgen.unwrapCasts(expr)) {
|
||||
is PtArrayIndexer -> {
|
||||
if (e.variable == null)
|
||||
throw AssemblyError("support for ptr indexing ${e.position}")
|
||||
val baseVarname = asmgen.asmVariableName(e.variable!!)
|
||||
val constIndex = e.index.asConstInteger()
|
||||
if (constIndex != null) {
|
||||
val idx = constIndex
|
||||
if (e.splitWords) {
|
||||
require(idx < 256)
|
||||
require(constIndex < 256)
|
||||
val suffix = if (long) "_3" else "_msb"
|
||||
asmgen.out(" lda $baseVarname$suffix+$idx")
|
||||
asmgen.out(" lda $baseVarname$suffix+$constIndex")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -964,7 +958,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" cpy #0")
|
||||
} else {
|
||||
var varname = asmgen.asmVariableName(e.identifier!!)
|
||||
if(e.identifier!!.type.isSplitWordArray) {
|
||||
if(e.identifier!!.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
varname += if(e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
}
|
||||
asmgen.out(" lda #>$varname")
|
||||
@@ -1580,7 +1574,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
@@ -1825,24 +1825,30 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
block("#<${constValue}", "#>${constValue}")
|
||||
} else {
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
if (e is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
block(varname, "$varname+1")
|
||||
} else if (e is PtAddressOf && !e.isFromArrayElement) {
|
||||
val identifier = e.identifier!!
|
||||
val varname = if (identifier.type.isSplitWordArray) {
|
||||
if (e.isMsbForSplitArray) identifier.name + "_msb" else identifier.name + "_lsb"
|
||||
} else {
|
||||
identifier.name
|
||||
when (e) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
block(varname, "$varname+1")
|
||||
}
|
||||
|
||||
is PtAddressOf if !e.isFromArrayElement && e.identifier != null -> {
|
||||
val identifier = e.identifier!!
|
||||
val varname = if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
if (e.isMsbForSplitArray) identifier.name + "_msb" else identifier.name + "_lsb"
|
||||
} else {
|
||||
identifier.name
|
||||
}
|
||||
block("#<$varname", "#>$varname")
|
||||
}
|
||||
|
||||
else -> {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
block("#<$varname", "#>$varname")
|
||||
} else {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
expr.type.isWord || expr.type.isString -> {
|
||||
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(trueLabel)
|
||||
@@ -104,8 +104,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
}
|
||||
|
||||
private fun evalIfExpressionConditonAndBranchWhenFalse(stmtCondition: PtExpression, falseLabel: String) {
|
||||
val condition = asmgen.unwrapCasts(stmtCondition)
|
||||
when (condition) {
|
||||
when (val condition = asmgen.unwrapCasts(stmtCondition)) {
|
||||
is PtBinaryExpression -> {
|
||||
val rightDt = condition.right.type
|
||||
return when {
|
||||
@@ -656,7 +655,13 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
@@ -717,7 +722,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
is PtAddressOf -> {
|
||||
val identifier = e.identifier!!
|
||||
var varname = asmgen.asmVariableName(identifier)
|
||||
if (identifier.type.isSplitWordArray) {
|
||||
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
}
|
||||
asmgen.out(" lda #<$varname | ora #>$varname | bne $falseLabel")
|
||||
@@ -768,7 +773,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
is PtAddressOf -> {
|
||||
val identifier = e.identifier!!
|
||||
var varname = asmgen.asmVariableName(identifier)
|
||||
if (identifier.type.isSplitWordArray) {
|
||||
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
}
|
||||
asmgen.out(" lda #<$varname | ora #>$varname | beq $falseLabel")
|
||||
@@ -796,8 +801,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
translateWordExprIsZero(expr, falseLabel)
|
||||
return
|
||||
}
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
when (e) {
|
||||
when (val e = asmgen.unwrapCasts(expr)) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
asmgen.out("""
|
||||
@@ -847,8 +851,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
translateWordExprIsNotZero(expr, falseLabel)
|
||||
return
|
||||
}
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
when (e) {
|
||||
when (val e = asmgen.unwrapCasts(expr)) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
asmgen.out("""
|
||||
@@ -1086,19 +1089,17 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(" lda $varname+$offset")
|
||||
return
|
||||
}
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
when (e) {
|
||||
when (val e = asmgen.unwrapCasts(expr)) {
|
||||
is PtArrayIndexer -> {
|
||||
if (e.variable == null)
|
||||
throw AssemblyError("support for ptr indexing ${e.position}")
|
||||
val baseVarname = asmgen.asmVariableName(e.variable!!)
|
||||
val constIndex = e.index.asConstInteger()
|
||||
if (constIndex != null) {
|
||||
val idx = constIndex
|
||||
if (e.splitWords) {
|
||||
require(idx < 256)
|
||||
require(constIndex < 256)
|
||||
val suffix = if (long) "_3" else "_msb"
|
||||
asmgen.out(" lda $baseVarname$suffix+$idx")
|
||||
asmgen.out(" lda $baseVarname$suffix+$constIndex")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1118,7 +1119,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(" cpy #0")
|
||||
} else {
|
||||
var varname = asmgen.asmVariableName(e.identifier!!)
|
||||
if(e.identifier!!.type.isSplitWordArray) {
|
||||
if(e.identifier!!.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
varname += if(e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
}
|
||||
asmgen.out(" lda #>$varname")
|
||||
@@ -1144,23 +1145,29 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
block("#<${constValue}", "#>${constValue}")
|
||||
} else {
|
||||
val e = asmgen.unwrapCasts(expr)
|
||||
if (e is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
block(varname, "$varname+1")
|
||||
} else if (e is PtAddressOf && !e.isFromArrayElement) {
|
||||
val identifier = e.identifier!!
|
||||
var varname = asmgen.asmVariableName(identifier)
|
||||
if (identifier.type.isSplitWordArray) {
|
||||
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
when (e) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(e)
|
||||
block(varname, "$varname+1")
|
||||
}
|
||||
|
||||
is PtAddressOf if !e.isFromArrayElement -> {
|
||||
val identifier = e.identifier!!
|
||||
var varname = asmgen.asmVariableName(identifier)
|
||||
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
|
||||
}
|
||||
block("#<$varname", "#>$varname")
|
||||
}
|
||||
|
||||
else -> {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
block("#<$varname", "#>$varname")
|
||||
} else {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" jsr p8_sys_startup.init_system")
|
||||
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
|
||||
}
|
||||
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
|
||||
}
|
||||
|
||||
if (options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
@@ -223,20 +224,23 @@ internal class ProgramAndVarsGen(
|
||||
private fun footer() {
|
||||
asmgen.out(" .dsection STRUCTINSTANCES\n")
|
||||
|
||||
var relocateBssVars = false
|
||||
var relocateBssSlabs = false
|
||||
var relocatedBssStart = 0u
|
||||
var relocatedBssEnd = 0u
|
||||
var relocateVars = false
|
||||
var relocatedStart = 0u
|
||||
var relocatedEnd = 0u
|
||||
|
||||
if(options.varsGolden) {
|
||||
if(options.varsAddress != null) {
|
||||
relocateVars = true
|
||||
relocatedStart = options.varsAddress!!
|
||||
relocatedEnd = if(options.memtopAddress != 0u) options.memtopAddress else 0xFFFFu
|
||||
} else if(options.varsGolden) {
|
||||
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||
}
|
||||
relocateBssVars = true
|
||||
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
relocateVars = true
|
||||
relocatedStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
}
|
||||
else if(options.varsHighBank!=null) {
|
||||
if(options.compTarget.BSSHIGHRAM_START == 0u ||
|
||||
@@ -244,67 +248,34 @@ internal class ProgramAndVarsGen(
|
||||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||
}
|
||||
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||
throw AssemblyError("slabs and vars high bank must be the same")
|
||||
relocateBssVars = true
|
||||
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
|
||||
}
|
||||
|
||||
if(options.slabsGolden) {
|
||||
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END == 0u ||
|
||||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||
}
|
||||
relocateBssSlabs = true
|
||||
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
|
||||
}
|
||||
else if(options.slabsHighBank!=null) {
|
||||
if(options.compTarget.BSSHIGHRAM_START == 0u ||
|
||||
options.compTarget.BSSHIGHRAM_END == 0u ||
|
||||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||
}
|
||||
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||
throw AssemblyError("slabs and vars high bank must be the same")
|
||||
relocateBssSlabs = true
|
||||
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
|
||||
relocateVars = true
|
||||
relocatedStart = options.compTarget.BSSHIGHRAM_START
|
||||
relocatedEnd = options.compTarget.BSSHIGHRAM_END
|
||||
}
|
||||
|
||||
asmgen.out("; bss sections")
|
||||
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
|
||||
if(relocateBssVars) {
|
||||
if(!relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
if(relocateVars) {
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||
asmgen.out(" * = ${relocatedStart.toHex()}")
|
||||
asmgen.out(" .dsection BSS_NOCLEAR")
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
if(relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedEnd.toHex()}, \"too many variables/data for BSS section\"")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
} else {
|
||||
asmgen.out(" .dsection BSS_NOCLEAR")
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
if(!relocateBssSlabs)
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
if(relocateBssSlabs) {
|
||||
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||
asmgen.out(" .dsection BSS_SLABS")
|
||||
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
|
||||
}
|
||||
}
|
||||
|
||||
if(relocatedBssEnd >= options.memtopAddress)
|
||||
options.memtopAddress = relocatedBssEnd+1u
|
||||
val effectiveEnd = if(relocateVars) relocatedEnd else 0u
|
||||
if(effectiveEnd >= options.memtopAddress && effectiveEnd != 0u)
|
||||
options.memtopAddress = effectiveEnd+1u
|
||||
|
||||
asmgen.out(" ; memtop check")
|
||||
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
|
||||
@@ -372,13 +343,14 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun asmTypeString(dt: DataType): String {
|
||||
val effectiveDt = if(dt.isArray) dt.elementType() else dt
|
||||
return when {
|
||||
dt.isBool || dt.isUnsignedByte -> ".byte"
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isLong -> ".dint"
|
||||
dt.isFloat -> ".byte"
|
||||
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
|
||||
effectiveDt.isSignedByte -> ".char"
|
||||
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
|
||||
effectiveDt.isSignedWord -> ".sint"
|
||||
effectiveDt.isLong -> ".dint"
|
||||
effectiveDt.isFloat -> ".byte"
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -387,60 +359,70 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun structInstances2asm() {
|
||||
|
||||
fun initValues(instance: StStructInstance): List<String> {
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
|
||||
if(field.first.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
"["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
} else {
|
||||
when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(field.first.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(field.first.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
fun formatValue(value: StArrayElement, dt: DataType): String {
|
||||
if(dt.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
}
|
||||
return when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(dt.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(dt.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
}
|
||||
|
||||
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
|
||||
var valueIndex = 0
|
||||
return structtype.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
(0 until count).map {
|
||||
val value = instance.initialValues[valueIndex++]
|
||||
formatValue(value, elemDt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asmgen.out("; struct types")
|
||||
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
|
||||
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
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
|
||||
if(structtype.isUnion) {
|
||||
// NB: .union takes no constructor parameters, unlike .struct
|
||||
asmgen.out("${structtype.scopedNameString} .union\n")
|
||||
emitStructDefFields(structtype) { "?" }
|
||||
asmgen.out(" .endunion\n")
|
||||
asmgen.out("${structtype.scopedNameString}_size=${structtype.size}\n")
|
||||
} else {
|
||||
val paramFields = structtype.fields.filter { !it.isArray }
|
||||
val structargs = paramFields.indices.joinToString(",") { "f$it" }
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
var paramIdx = 0
|
||||
emitStructDefFields(structtype) { "\\f${paramIdx++}" }
|
||||
asmgen.out(" .endstruct\n")
|
||||
asmgen.out("${structtype.scopedNameString}_size=${structtype.size}\n")
|
||||
}
|
||||
asmgen.out(" .endstruct\n")
|
||||
}
|
||||
|
||||
val (instancesNoInit, instances) = symboltable.allStructInstances().partition { it.initialValues.isEmpty() }
|
||||
val (instancesNoInit, instances) = symboltable.allStructInstances().partition {
|
||||
val structtype = symboltable.lookup(it.structName) as StStruct
|
||||
it.initialValues.isEmpty() || structtype.isUnion
|
||||
}
|
||||
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 ->
|
||||
if(field.first.isFloat) {
|
||||
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
|
||||
"[${floatbytes.joinToString(",")}]"
|
||||
}
|
||||
else "?"
|
||||
}
|
||||
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
|
||||
asmgen.out("${it.name} .fill ${structtype.size} ; struct ${it.structName}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send BSS\n")
|
||||
@@ -448,14 +430,54 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; struct instances with initialization values\n")
|
||||
asmgen.out(" .section STRUCTINSTANCES\n")
|
||||
asmgen.out("$StStructInstanceBlockName .block\n")
|
||||
instances.forEach {
|
||||
val instancename = it.name.substringAfter('.')
|
||||
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
|
||||
instances.forEach { instance ->
|
||||
val instancename = instance.name.substringAfter('.')
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
val values = flatInitValues(instance, structtype)
|
||||
val hasArrays = structtype.fields.any { fld -> fld.isArray }
|
||||
if(hasArrays) {
|
||||
asmgen.out(instancename)
|
||||
var valueIndex = 0
|
||||
for(field in structtype.fields) {
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
val type = asmTypeString(elemDt)
|
||||
val fieldValues = (0 until count).joinToString(",") {
|
||||
formatValue(instance.initialValues[valueIndex++], elemDt)
|
||||
}
|
||||
asmgen.out(" $type $fieldValues")
|
||||
}
|
||||
asmgen.out("\n")
|
||||
} else {
|
||||
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send STRUCTINSTANCES\n")
|
||||
}
|
||||
|
||||
private fun emitStructDefFields(structtype: StStruct, scalarValue: () -> String) {
|
||||
for(field in structtype.fields) {
|
||||
val effectiveDt = if(field.isArray) field.type.elementType() else field.type
|
||||
if(effectiveDt.isFloat) {
|
||||
val floatSize = compTarget.FLOAT_MEM_SIZE.toInt()
|
||||
if(field.isArray)
|
||||
asmgen.out("p8v_${field.name} .fill ${floatSize*field.arraySize!!}, ?")
|
||||
else
|
||||
asmgen.out("p8v_${field.name} .fill $floatSize, ${scalarValue()}")
|
||||
} else {
|
||||
val type = asmTypeString(field.type)
|
||||
if(field.isArray) {
|
||||
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
|
||||
asmgen.out("p8v_${field.name} $type $anonymous")
|
||||
} else {
|
||||
asmgen.out("p8v_${field.name} $type ${scalarValue()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateAsmSubroutine(sub: PtAsmSub) {
|
||||
if(sub.inline) {
|
||||
return // subroutine gets inlined at call site.
|
||||
@@ -617,18 +639,26 @@ internal class ProgramAndVarsGen(
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val size = it.alloc.size
|
||||
val name = asmgen.asmVariableName(it.name)
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
if(it.alloc.dt.isSplitWordArray(compTarget)) {
|
||||
val halfSize = size / 2
|
||||
if(halfSize>0) {
|
||||
asmgen.out("""
|
||||
ldx #${halfSize-1}
|
||||
- lda ${name}_init_value_lsb,x
|
||||
sta ${name}_lsb,x
|
||||
lda ${name}_init_value_msb,x
|
||||
sta ${name}_msb,x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldx #${size-1}
|
||||
- lda ${name}_init_value,x
|
||||
sta ${name},x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
}
|
||||
asmgen.out(" jmp +")
|
||||
}
|
||||
@@ -688,7 +718,7 @@ internal class ProgramAndVarsGen(
|
||||
if (scopedName.startsWith("cx16.r"))
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(variable.dt.isSplitWordArray) {
|
||||
if(variable.dt.isSplitWordArray(compTarget)) {
|
||||
val lsbAddr = zpvar.address
|
||||
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
|
||||
@@ -780,21 +810,19 @@ internal class ProgramAndVarsGen(
|
||||
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 -> {
|
||||
dt.isSplitWordArray(compTarget) -> {
|
||||
alignVar(variable.align)
|
||||
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (lsbs)")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (msbs)")
|
||||
}
|
||||
dt.isArray -> {
|
||||
alignVar(variable.align)
|
||||
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
|
||||
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||
val amount = variable.length!!.toInt()
|
||||
val elementSize = compTarget.memorySize(variable.dt.elementType(), null)
|
||||
asmgen.out("${variable.name}\t.fill $amount * $elementSize ; array of $amount ${variable.dt.elementType()} ")
|
||||
}
|
||||
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
|
||||
dt.isPointerArray -> {
|
||||
TODO("pointers are not supported yet for uninitialized array ${variable.astNode?.position}")
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -871,17 +899,17 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .char " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
dt.isSplitWordArray -> {
|
||||
dt.isSplitWordArray(compTarget) -> {
|
||||
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")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
} else {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedWordArray -> {
|
||||
@@ -925,6 +953,50 @@ internal class ProgramAndVarsGen(
|
||||
for (f in array.zip(floatFills))
|
||||
asmgen.out(" .byte ${f.second} ; float ${f.first}")
|
||||
}
|
||||
dt.base==BaseDataType.ARRAY && dt.sub==BaseDataType.STRUCT_INSTANCE -> {
|
||||
val struct = dt.subType as? StStruct ?: throw AssemblyError("struct type not found for $varname")
|
||||
val structSize = struct.size.toInt()
|
||||
if(value==null) {
|
||||
val totalBytes = (orNumberOfZeros ?: 0) * structSize
|
||||
if(totalBytes==0) {
|
||||
asmgen.out("$varname: .byte 0")
|
||||
} else {
|
||||
asmgen.out("$varname: .fill $totalBytes, 0")
|
||||
}
|
||||
} else {
|
||||
// initialized struct array: value contains StructInstance elements
|
||||
asmgen.out("$varname ; array of ${value.size} ${struct.name} structs")
|
||||
// expand struct field types for per-element handling
|
||||
val expandedFieldTypes = struct.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
if(arraySz!=null) List(arraySz) { field.type } else listOf(field.type)
|
||||
}
|
||||
for(elt in value) {
|
||||
val instanceName = (elt as? StArrayElement.StructInstance)?.name ?: throw AssemblyError("expected struct instance in array init for $varname")
|
||||
val instance = symboltable.lookup(instanceName) as? StStructInstance ?: throw AssemblyError("struct instance $instanceName not found")
|
||||
val fieldValues = instance.initialValues
|
||||
require(fieldValues.size==expandedFieldTypes.size) { "field count mismatch for $instanceName" }
|
||||
for((fieldType, fieldVal) in expandedFieldTypes.zip(fieldValues)) {
|
||||
val num = when(fieldVal) {
|
||||
is StArrayElement.Number -> fieldVal.value
|
||||
is StArrayElement.BoolValue -> if(fieldVal.value) 1.0 else 0.0
|
||||
else -> throw AssemblyError("struct field value must be number for $varname (got $fieldVal)")
|
||||
}
|
||||
when {
|
||||
fieldType.isByteOrBool -> asmgen.out(" .byte ${num.toInt()}")
|
||||
fieldType.isWord -> asmgen.out(" .word ${num.toInt()}")
|
||||
fieldType.isLong -> asmgen.out(" .dword ${num.toInt()}")
|
||||
fieldType.isFloat -> {
|
||||
val bytes = compTarget.getFloatAsmBytes(num)
|
||||
asmgen.out(" .byte $bytes ; float $num")
|
||||
}
|
||||
fieldType.isPointer -> asmgen.out(" .word ${num.toInt()} ; pointer")
|
||||
else -> throw AssemblyError("unsupported struct field type $fieldType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("require array dt")
|
||||
}
|
||||
}
|
||||
@@ -940,14 +1012,14 @@ internal class ProgramAndVarsGen(
|
||||
consts.sortedBy { it.name }.forEach {
|
||||
val nameWithoutColons = it.name.replace("::", "_")
|
||||
if(it.dt.isFloat)
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value}")
|
||||
asmgen.out(" $nameWithoutColons = ${it.value}")
|
||||
else {
|
||||
if(it.value!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
|
||||
asmgen.out(" $nameWithoutColons = ${it.value!!.toHex()}")
|
||||
else if(it.memorySlab!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
|
||||
asmgen.out(" $nameWithoutColons = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
|
||||
else
|
||||
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
|
||||
TODO("asm gen for $nameWithoutColons = memory alloc?")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -996,7 +1068,7 @@ internal class ProgramAndVarsGen(
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
@@ -1037,7 +1109,7 @@ internal class ProgramAndVarsGen(
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
|
||||
@@ -3,17 +3,21 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onOk
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
private val errors: IErrorReporter,
|
||||
private val program: PtProgram
|
||||
) {
|
||||
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||
private val allStaticVariables by lazy { collectAllVariables(symboltable) }
|
||||
private val usageScores by lazy { computeUsageScores(allStaticVariables) }
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
@@ -25,7 +29,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
return true
|
||||
|
||||
val v = symboltable.lookup(scopedName)
|
||||
return if(v is StMemVar) v.address <= 255u else false
|
||||
return v is StMemVar && v.address <= 255u
|
||||
}
|
||||
|
||||
internal fun getFloatAsmConst(number: Double): String {
|
||||
@@ -46,7 +50,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val allVariables = collectAllVariables(symboltable)
|
||||
val allVariables = allStaticVariables
|
||||
|
||||
val numberOfAllocatableVariables = allVariables.size
|
||||
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
@@ -90,9 +94,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
}
|
||||
|
||||
// try to allocate the "don't care" interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
|
||||
// Prioritize by usage frequency: sort by computed hotness score (access frequency × type weight × loop depth)
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { usageScores[it.scopedNameString] ?: 0 }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
@@ -119,14 +123,49 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
private fun collectAllVariables(st: SymbolTable): Collection<StStaticVariable> {
|
||||
val vars = mutableListOf<StStaticVariable>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type == StNodeType.STATICVAR)
|
||||
vars.add(child.value as StStaticVariable)
|
||||
for((_, value) in node.children) {
|
||||
if(value.type == StNodeType.STATICVAR)
|
||||
vars.add(value as StStaticVariable)
|
||||
else
|
||||
collect(child.value)
|
||||
collect(value)
|
||||
}
|
||||
}
|
||||
collect(st)
|
||||
return vars.sortedBy { it.dt.base }
|
||||
}
|
||||
|
||||
private fun computeUsageScores(vars: Collection<StStaticVariable>): Map<String, Int> {
|
||||
val scores = mutableMapOf<String, Int>()
|
||||
// Note: by the time codegen runs, the simple-AST identifier names are already fully scoped
|
||||
// AND symbol-prefixed ("p8b_maze.p8s_solve.p8v_cx"), so a plain lookup by PtIdentifier.name
|
||||
// correctly matches the scopedNameString keys used here. This looks like a name/scope
|
||||
// mismatch bug but is not: resolving identifiers to their enclosing scope instead (or
|
||||
// keying this map by the bare variable name) silently makes every score zero, which
|
||||
// degenerates the sort in allocateZeropageVariables() into a stable no-op.
|
||||
val varInfo = vars.associateBy { it.scopedNameString }
|
||||
var loopDepth = 0
|
||||
|
||||
fun walk(node: PtNode) {
|
||||
val isLoopEnter = node is PtForLoop || node is PtRepeatLoop
|
||||
if(isLoopEnter) loopDepth++
|
||||
|
||||
if(node is PtIdentifier) {
|
||||
val v = varInfo[node.name]
|
||||
if(v != null) {
|
||||
val weight = when {
|
||||
v.dt.isPointer || v.dt.isWord -> 2
|
||||
else -> 1
|
||||
}
|
||||
scores[node.name] = (scores[node.name] ?: 0) + weight * (loopDepth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach { walk(it) }
|
||||
|
||||
if(isLoopEnter) loopDepth--
|
||||
}
|
||||
|
||||
walk(program)
|
||||
return scores
|
||||
}
|
||||
}
|
||||
@@ -125,10 +125,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
|
||||
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
// C64/PET32 ROM T-variant entries skip CONUPK and need arisgn+Z flag set up beforehand.
|
||||
// CX16 ROM entries are already fixed and handle this internally.
|
||||
val tSuffix = if(asmgen.options.compTarget.name in listOf(C64Target.NAME, PETTarget.NAME)) "_NZ" else ""
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FADDT")
|
||||
asmgen.out(" jsr floats.FADDT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
@@ -140,13 +143,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
"*" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FMULTT")
|
||||
asmgen.out(" jsr floats.FMULTT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"/" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FDIVT")
|
||||
asmgen.out(" jsr floats.FDIVT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package prog8.codegen.cpu6502.assignment
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
|
||||
@@ -237,7 +236,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
if(sub is PtSub && sub.signature.returns.size>1)
|
||||
DataType.UNDEFINED // TODO list of types instead?
|
||||
else
|
||||
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
sub.returnsWhatWhere(asmgen.options.compTarget).firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+339
-21
@@ -2,6 +2,8 @@ package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.cpu6502.byteShiftAddExpansion
|
||||
import prog8.code.cpu6502.wordShiftAddExpansion
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8.codegen.cpu6502.toLongHex
|
||||
@@ -62,6 +64,153 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModificationIndexedPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
val array = target.array!!
|
||||
val elementDt = target.datatype
|
||||
if(!elementDt.isByteOrBool && !elementDt.isWord)
|
||||
throw AssemblyError("inplace modification indexed pointer field for type $elementDt not supported")
|
||||
val indexedTarget = IndexedPtrTarget(target)
|
||||
val ptr = ptrgen.indexedEffectiveAddress(indexedTarget)
|
||||
|
||||
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, array)
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($ptr),y
|
||||
sta $elementTemp+1""")
|
||||
}
|
||||
|
||||
// save the computed address on the hardware stack so later code can't clobber it
|
||||
asmgen.out("""
|
||||
lda $ptr
|
||||
pha
|
||||
lda $ptr+1
|
||||
pha""")
|
||||
|
||||
// reuse the existing variable-target logic to modify the temp
|
||||
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
|
||||
inplaceModification(tempTarget, operator, value)
|
||||
|
||||
// restore the address and store the temp back
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta $ptr+1
|
||||
pla
|
||||
sta $ptr""")
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModificationPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
// the pointer to the target field is materialized in the shared scratch pointer
|
||||
// P8ZP_SCRATCH_PTR because the pointer variable isn't in the zeropage (or the
|
||||
// deref chain is too complex). Evaluating the source value may clobber that
|
||||
// scratch pointer, so we operate on a temporary variable and preserve the
|
||||
// address on the hardware stack, mirroring inplaceModificationIndexedPointerField.
|
||||
val elementDt = target.datatype
|
||||
if(!elementDt.isByteOrBool && !elementDt.isWord && !elementDt.isLong)
|
||||
throw AssemblyError("inplace modification of pointer field for type $elementDt not supported ${target.position}")
|
||||
val ptrTarget = PtrTarget(target)
|
||||
val (zpPtrVar, offset) = ptrgen.deref(ptrTarget.pointer)
|
||||
require(zpPtrVar=="P8ZP_SCRATCH_PTR")
|
||||
|
||||
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, target.origAstTarget ?: target.pointer!!)
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp""")
|
||||
} else if(elementDt.isWord) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp+1""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta $elementTemp+3""")
|
||||
}
|
||||
|
||||
// save the computed address on the hardware stack so later code can't clobber it
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
pha
|
||||
lda $zpPtrVar+1
|
||||
pha""")
|
||||
|
||||
// reuse the existing variable-target logic to modify the temp
|
||||
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
|
||||
inplaceModification(tempTarget, operator, value)
|
||||
|
||||
// restore the address and store the temp back
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta $zpPtrVar+1
|
||||
pla
|
||||
sta $zpPtrVar""")
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda $elementTemp
|
||||
sta ($zpPtrVar),y""")
|
||||
} else if(elementDt.isWord) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda $elementTemp
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda $elementTemp
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda $elementTemp+2
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda $elementTemp+3
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
@@ -247,7 +396,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
TODO("inplace modification array indexed pointer deref ${target.position}")
|
||||
inplaceModificationIndexedPointerField(target, operator, value)
|
||||
return
|
||||
}
|
||||
val targetArrayVar = target.array.variable!!
|
||||
@@ -318,14 +467,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
SourceStorageKind.EXPRESSION ->
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,13 +703,47 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> ptrgen.inplaceModification(PtrTarget(target), operator, value)
|
||||
TargetStorageKind.POINTER -> {
|
||||
// if the effective target address ends up in the shared scratch pointer
|
||||
// P8ZP_SCRATCH_PTR, evaluating the source value could clobber it. In that
|
||||
// case operate on a protected temporary value instead.
|
||||
val usesOffsetPointer = target.datatype.isFloat || operator in setOf("*", "/", "%")
|
||||
val needsProtectedPtr = ptrgen.derefUsesScratchPointer(target.pointer!!, usesOffsetPointer)
|
||||
if (value.kind in setOf(SourceStorageKind.EXPRESSION, SourceStorageKind.REGISTER)
|
||||
&& needsProtectedPtr
|
||||
&& (target.datatype.isByteOrBool || target.datatype.isWord || target.datatype.isLong))
|
||||
inplaceModificationPointerField(target, operator, value)
|
||||
else
|
||||
ptrgen.inplaceModification(PtrTarget(target), operator, value)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
|
||||
// Adding an unsigned byte to a long: skip zero-extending the byte into a
|
||||
// full 4-byte long and just add it with carry propagation through the upper bytes.
|
||||
// The byte shows up here as a (widened) long-typed cast of an ubyte expression,
|
||||
// so detect it via the cast child type.
|
||||
if(operator=="+" && value is PtTypeCast && value.value.type==DataType.UBYTE) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value.value, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $targetVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
adc #0
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc #0
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc #0
|
||||
sta $targetVar+3
|
||||
""")
|
||||
return
|
||||
}
|
||||
// it's not an expression so no need to preserve R14-R15
|
||||
if(value.type.isSigned) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
@@ -577,24 +759,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
clc
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
adc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
adc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
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
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
sec
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
sbc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
sbc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
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("""
|
||||
@@ -667,6 +865,53 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to target
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $targetVar
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $targetVar+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $targetVar+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable, operator=$operator")
|
||||
}
|
||||
@@ -869,8 +1114,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $variable
|
||||
lda $variable+3
|
||||
bpl +
|
||||
lda #255
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $variable
|
||||
sta $variable+1
|
||||
sta $variable+2
|
||||
sta $variable+3""")
|
||||
@@ -1021,6 +1270,65 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
// long /= constant - use division routine
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
// long %= constant - use division routine, remainder is in P8ZP_SCRATCH_W1/W2
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to variable
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $variable
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $variable+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $variable+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $variable+3""")
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
@@ -1549,7 +1857,7 @@ $shortcutLabel:""")
|
||||
// use the already existing optimized codegen for regular assignments x += array[index]
|
||||
val binexpr = PtBinaryExpression(operator, dt, value.position)
|
||||
binexpr.add(PtIdentifier(name, dt, value.position))
|
||||
val arrayValue = PtArrayIndexer(value.type, value.position)
|
||||
val arrayValue = PtArrayIndexer(value.type, valueVar.type.isSplitWordArray(program.memsizer), value.position)
|
||||
arrayValue.add(valueVar)
|
||||
arrayValue.add(value.index)
|
||||
binexpr.add(arrayValue)
|
||||
@@ -1939,6 +2247,11 @@ $shortcutLabel:""")
|
||||
"*" -> {
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" lda $name | jsr prog8_math.mul_byte_$value | sta $name")
|
||||
else if(byteShiftAddExpansion(value)!=null) {
|
||||
asmgen.out(" lda $name")
|
||||
asmgen.emitByteShiftAdd(value)
|
||||
asmgen.out(" sta $name")
|
||||
}
|
||||
else
|
||||
asmgen.out(" lda $name | ldy #$value | jsr prog8_math.multiply_bytes | sta $name")
|
||||
}
|
||||
@@ -2250,8 +2563,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) {
|
||||
// cx16 verafx hardware mul
|
||||
if(block?.options?.veraFxMuls==true) { // cx16 verafx hardware mul
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
@@ -2265,6 +2577,11 @@ $shortcutLabel:""")
|
||||
sta $lsb
|
||||
sty $msb""")
|
||||
}
|
||||
else if(wordShiftAddExpansion(value)!=null) {
|
||||
asmgen.out(" lda $lsb | ldy $msb")
|
||||
asmgen.emitWordShiftAdd(value)
|
||||
asmgen.out(" sta $lsb | sty $msb")
|
||||
}
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
@@ -3149,6 +3466,7 @@ $shortcutLabel:""")
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
oweroff
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -113,34 +113,136 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun inplaceByteInvert(target: PtrTarget) {
|
||||
TODO("inplace byte invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
val eorVal = if(target.dt.isBool) 1 else 255
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out(" lda ($zpPtrVar) | eor #$eorVal | sta ($zpPtrVar)")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #$eorVal
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceWordInvert(target: PtrTarget) {
|
||||
TODO("inplace word invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace byte negate to pointer deref ${target.position}")
|
||||
internal fun inplaceLongInvert(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace word negate pointer deref ${target.position}")
|
||||
internal fun inplaceByteNegate(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
ina
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace long negate pointer deref ${target.position}")
|
||||
internal fun inplaceWordNegate(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace float negate pointer deref ${target.position}")
|
||||
internal fun inplaceLongNegate(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceFloatNegate(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
// flip sign bit in second byte of 5-byte float
|
||||
asmgen.out(
|
||||
$$"""
|
||||
ldy #$$offset
|
||||
iny
|
||||
lda ($$zpPtrVar),y
|
||||
eor #$80
|
||||
sta ($$zpPtrVar),y"""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
|
||||
// walk the pointer deref chain and leaves the final pointer value in a ZP var
|
||||
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
|
||||
/**
|
||||
* Walk the pointer deref chain. Always returns (zpVar, offset) such that one indirect
|
||||
* access through zpVar at byte offset reaches the final thing:
|
||||
* derefLast=true -> (zpVar),offset is the address of the value itself
|
||||
* derefLast=false -> (zpVar),offset is the location of the field content
|
||||
* This contract holds for every chain length: the final pointer (if any) is already
|
||||
* followed inside deref(), so callers never need a second dereference.
|
||||
* zpVar is P8ZP_SCRATCH_PTR for non-empty chains or non-zeropage pointers; it may be
|
||||
* the original (zeropage) pointer variable only for an empty chain with derefLast=false.
|
||||
* loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final
|
||||
* field is a pointer-typed field (rather than returning its address).
|
||||
*/
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
|
||||
|
||||
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
|
||||
if(fieldoffset==0u)
|
||||
@@ -185,7 +287,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(addOffsetToPointer) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
if(pointer.derefLast)
|
||||
if(pointer.derefLast || (loadPointerFieldValue && field.first.isPointer))
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else {
|
||||
@@ -193,20 +295,22 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(pointer.derefLast) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
}
|
||||
else
|
||||
return pointer.startpointer.name to field.second
|
||||
return pointer.startpointer.name to field.second.toUByte()
|
||||
}
|
||||
else {
|
||||
// have to copy it to temp zp var
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
if(pointer.derefLast) {
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
}
|
||||
else
|
||||
return "P8ZP_SCRATCH_PTR" to field.second
|
||||
return "P8ZP_SCRATCH_PTR" to field.second.toUByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,22 +339,75 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
} else {
|
||||
if(addOffsetToPointer) {
|
||||
addFieldOffsetToScratchPointer(fieldinfo.second.toUInt())
|
||||
if(loadPointerFieldValue && fieldinfo.first.isPointer)
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else
|
||||
return "P8ZP_SCRATCH_PTR" to fieldinfo.second
|
||||
return "P8ZP_SCRATCH_PTR" to fieldinfo.second.toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
|
||||
val (zpPtrVar, offset1) = deref(value)
|
||||
val offset: UByte
|
||||
if(value.derefLast) {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset1)
|
||||
asmgen.out(" sta $zpPtrVar | sty $zpPtrVar+1")
|
||||
offset = 0.toUByte()
|
||||
} else {
|
||||
offset = offset1
|
||||
internal fun derefUsesScratchPointer(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Boolean {
|
||||
// determine if deref() would need to keep the effective address in the shared
|
||||
// scratch pointer P8ZP_SCRATCH_PTR, which a subsequent evaluation of a source
|
||||
// expression could then clobber. Only if it doesn't, may the pointer itself (if
|
||||
// located in the zeropage) keep being used for the read-modify-write.
|
||||
if(pointer.chain.isEmpty())
|
||||
return !allocator.isZpVar(pointer.startpointer.name)
|
||||
if(pointer.chain.size > 1 || addOffsetToPointer)
|
||||
return true
|
||||
// chain==1 derefLast=true now fully follows into P8ZP_SCRATCH_PTR (see deref()),
|
||||
// so it always uses the scratch pointer regardless of isZpVar.
|
||||
return !(allocator.isZpVar(pointer.startpointer.name) && !pointer.derefLast)
|
||||
}
|
||||
|
||||
internal fun indexedEffectiveAddress(target: IndexedPtrTarget): String {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
// generic: evaluate index as word, scale by element size, add field-corrected base pointer
|
||||
asmgen.assignExpressionToVariable(target.index, "P8ZP_SCRATCH_W1", DataType.UWORD)
|
||||
when (eltSize) {
|
||||
1 -> {}
|
||||
2 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
4 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
8 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
else -> {
|
||||
// generic multiply by eltSize
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda #<$eltSize
|
||||
ldy #>$eltSize
|
||||
jsr prog8_math.multiply_words
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
}
|
||||
}
|
||||
// save scaled index to W2, compute base (field-corrected) into P8ZP_SCRATCH_PTR, then add
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta P8ZP_SCRATCH_W2 | lda P8ZP_SCRATCH_W1+1 | sta P8ZP_SCRATCH_W2+1")
|
||||
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
if(basePtr != "P8ZP_SCRATCH_PTR") {
|
||||
// deref returned the original zp pointer variable; copy it so we don't mutate it
|
||||
asmgen.out("""
|
||||
lda $basePtr
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $basePtr+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda P8ZP_SCRATCH_PTR+1
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
return "P8ZP_SCRATCH_PTR"
|
||||
}
|
||||
|
||||
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
|
||||
val (zpPtrVar, offset) = deref(value)
|
||||
if(value.type.isByteOrBool) {
|
||||
asmgen.loadIndirectByte(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
@@ -309,24 +466,24 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
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.isWord || target.dt.isPointer) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else if (target.dt.isLong) inplaceLongAdd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
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.isWord || target.dt.isPointer) inplaceWordSub(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else if (target.dt.isLong) inplaceLongSub(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
@@ -409,132 +566,163 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignByte(target: IndexedPtrTarget, byte: Int) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
// need to add index to the pointer (but not clobber it) to allow index>255
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) { // TODO not very robust....
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
// element size is 1, can immediately add the index value
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectByte(byte, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
|
||||
TODO("array ptr assign const word ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWord(word, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
TODO("array ptr assign const long ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
TODO("array ptr assign const float ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloat(float, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign FAC1 ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatFP1(ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFloatAY(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign float AY ${target.position}")
|
||||
// A/Y contains a pointer to the source float. Save it, compute destination address,
|
||||
// restore source pointer into W1, load destination pointer into A/Y, and copy.
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" pha | phy")
|
||||
else
|
||||
asmgen.out(" pha | tya | pha")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ply
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFloatVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign float var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatVar(varName, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteReg(target: IndexedPtrTarget, register: CpuRegister) {
|
||||
// save register on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
TODO("array ptr assign byte reg ${target.position}")
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.restoreRegisterStack(register, false)
|
||||
asmgen.storeIndirectByteReg(register, ptr, 0u, false, false)
|
||||
}
|
||||
|
||||
internal fun assignWordReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// save word reg on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
saveOnStack(regs)
|
||||
TODO("array ptr assign word reg ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
restoreFromStack(regs)
|
||||
asmgen.storeIndirectWordReg(regs, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteVar(target: IndexedPtrTarget, varName: String, extendToWord: Boolean, signed: Boolean) {
|
||||
TODO("array ptr assign byte var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(extendToWord) {
|
||||
// need to store byte var extended to word
|
||||
asmgen.out(" lda $varName")
|
||||
if(signed) asmgen.signExtendAXlsb(BaseDataType.BYTE) else asmgen.signExtendAXlsb(BaseDataType.UBYTE)
|
||||
asmgen.storeIndirectWordReg(RegisterOrPair.AX, ptr, 0u)
|
||||
} else {
|
||||
asmgen.storeIndirectByteVar(varName, ptr, 0u)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignWordVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign word var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWordVar(varName, DataType.UWORD, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign long var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $varName
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// generic fallback for long array via pointer
|
||||
val startreg = regs.startregname()
|
||||
// save source long on the stack in case index expression evaluation uses the same registers
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
pha
|
||||
lda cx16.$startreg+1
|
||||
pha
|
||||
lda cx16.$startreg+2
|
||||
pha
|
||||
lda cx16.$startreg+3
|
||||
pha""")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta cx16.$startreg+3
|
||||
pla
|
||||
sta cx16.$startreg+2
|
||||
pla
|
||||
sta cx16.$startreg+1
|
||||
pla
|
||||
sta cx16.$startreg
|
||||
ldy #0
|
||||
lda cx16.$startreg
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongVar(pointer: PtPointerDeref, varName: String) {
|
||||
@@ -774,7 +962,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val right = binExpr.right as? PtIdentifier
|
||||
require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"}
|
||||
|
||||
val field: Pair<DataType, UByte>
|
||||
val field: Pair<DataType, Int>
|
||||
var extraFieldOffset = 0
|
||||
|
||||
if(left.type.isStructInstance) {
|
||||
@@ -805,7 +993,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
field = struct.getField(right.name, asmgen.program.memsizer)
|
||||
}
|
||||
|
||||
val offset = extraFieldOffset + field.second.toInt()
|
||||
val offset = extraFieldOffset + field.second
|
||||
// TODO avoid always using a zp scratch reg below if the only thing we dereferenced above is a pointer variable that is already in zeropage itself
|
||||
if(offset>=256) {
|
||||
// add field offset to pointer if it doesn't fit in the index register
|
||||
@@ -2560,7 +2748,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer) {
|
||||
// Load value from array[index] and store to pointer target
|
||||
if(target.datatype.isLong) {
|
||||
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,860 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
internal class TypeCastAssignmentsGen(
|
||||
private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val pointergen: PointerAssignmentsGen
|
||||
) {
|
||||
lateinit var assignmentAsmGen: AssignmentAsmGen
|
||||
|
||||
internal fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: PtExpression, origTypeCastExpression: PtTypeCast) {
|
||||
val valueDt = value.type
|
||||
if(valueDt==targetDt)
|
||||
throw AssemblyError("type cast to identical dt should have been removed: $valueDt at ${value.position}")
|
||||
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
if(targetDt.isWord) {
|
||||
if(valueDt.isUnsignedByte || valueDt.isBool) {
|
||||
assignmentAsmGen.assignVariableUByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
if(valueDt.isSignedByte) {
|
||||
assignmentAsmGen.assignVariableByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if(targetDt.isWord) {
|
||||
|
||||
fun assignViaExprEval(addressExpression: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" ldy #0")
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
when (value.address) {
|
||||
is PtNumber -> {
|
||||
val address = (value.address as PtNumber).number.toUInt()
|
||||
assignmentAsmGen.assignMemoryByteIntoWord(target, address, null)
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignmentAsmGen.assignMemoryByteIntoWord(target, null, value.address as PtIdentifier)
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
val addrExpr = value.address as PtBinaryExpression
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
|
||||
asmgen.out(" ldy #0")
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
} else {
|
||||
assignViaExprEval(value.address)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
assignViaExprEval(value.address)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
is PtNumber -> {
|
||||
if(targetDt.isPointer) {
|
||||
// assign a number to a pointer type
|
||||
require(valueDt.isInteger)
|
||||
return assignmentAsmGen.assignConstantWord(target, value.number.toInt())
|
||||
}
|
||||
else
|
||||
throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
|
||||
}
|
||||
is PtBool -> throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
|
||||
is PtArrayIndexer -> {
|
||||
if(targetDt.isByte && valueDt.isWord) {
|
||||
// just assign the lsb from the array value
|
||||
return assignCastWordViaLsbFunc(value, target)
|
||||
} else if(targetDt.isBool && valueDt.isWord) {
|
||||
return assignWordToBool(value, target)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
|
||||
// special case optimizations
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
if(value is PtIdentifier && !valueDt.isUndefined)
|
||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||
|
||||
when {
|
||||
valueDt.isBool -> {
|
||||
if(targetDt.isInteger) {
|
||||
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
|
||||
val assignDirect = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen),
|
||||
listOf(target),
|
||||
program.memsizer,
|
||||
target.position
|
||||
)
|
||||
assignmentAsmGen.assignExpression(assignDirect, target.scope)
|
||||
} else {
|
||||
TODO("assign bool to non-integer type $targetDt ${value.position}")
|
||||
}
|
||||
}
|
||||
valueDt.isByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isWord || valueDt.isPointer -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base, target.position)
|
||||
}
|
||||
valueDt.isFloat -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
|
||||
assignTypeCastedFloatFAC1(target.asmVarname, targetDt.base)
|
||||
}
|
||||
valueDt.isPassByRef -> {
|
||||
// str/array value cast (most likely to UWORD, take address-of)
|
||||
assignmentAsmGen.assignExpressionToVariable(value, target.asmVarname, targetDt)
|
||||
}
|
||||
else -> throw AssemblyError("strange dt in typecast assign to var: $valueDt --> $targetDt")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(valueDt.isWord && origTypeCastExpression.type.isUnsignedByte) {
|
||||
val parentTc = origTypeCastExpression.parent as? PtTypeCast
|
||||
if(parentTc!=null && parentTc.type.isUnsignedWord) {
|
||||
// typecast a word value to ubyte and directly back to uword
|
||||
// generate code for lsb(value) here instead of the ubyte typecast
|
||||
return assignCastWordViaLsbFunc(value, target)
|
||||
}
|
||||
}
|
||||
|
||||
if(valueDt.isByteOrBool) {
|
||||
when(target.register) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> {
|
||||
// 'cast' an ubyte value to a byte register; no cast needed at all
|
||||
return assignmentAsmGen.assignExpressionToRegister(value, target.register, valueDt.isSigned)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
}
|
||||
in CombinedLongRegisters -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
} else if(valueDt.isWord) {
|
||||
when(target.register) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> {
|
||||
return if(targetDt.isBool)
|
||||
assignWordToBool(value, target)
|
||||
else
|
||||
// cast an uword to a byte register, do this via lsb(value)
|
||||
// generate code for lsb(value) here instead of the ubyte typecast
|
||||
assignCastWordViaLsbFunc(value, target)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
// 'cast' uword into a 16 bits register, just assign it
|
||||
return assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
}
|
||||
RegisterOrPair.R0R1 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R2R3 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R2, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R4R5 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R4, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R6R7 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R6, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R8R9 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R8, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R10R11 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R10, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R12R13 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
|
||||
}
|
||||
RegisterOrPair.R14R15 -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14, false)
|
||||
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(target.kind==TargetStorageKind.REGISTER) {
|
||||
if(valueDt.isFloat && !target.datatype.isFloat) {
|
||||
// have to typecast the float number on the fly down to an integer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, targetDt.isSigned)
|
||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", targetDt.base)
|
||||
assignmentAsmGen.assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, targetDt.isSigned, origTypeCastExpression.definingISub(), target.position)
|
||||
return
|
||||
} else {
|
||||
if(!(valueDt isAssignableTo targetDt)) {
|
||||
return if(valueDt.isWord && targetDt.isByte) {
|
||||
// word to byte, just take the lsb
|
||||
assignCastWordViaLsbFunc(value, target)
|
||||
} else if(valueDt.isWord && targetDt.isBool) {
|
||||
// word to bool
|
||||
assignWordToBool(value, target)
|
||||
} else if(valueDt.isWord && targetDt.isWord) {
|
||||
// word to word, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isByteOrBool && targetDt.isByte) {
|
||||
// byte to byte, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isByteOrBool && targetDt.isWord) {
|
||||
// byte to word, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else if(valueDt.isLong && targetDt.isByte) {
|
||||
// long to byte, just take the lsb
|
||||
assignCastLongToByte(value, target)
|
||||
} else if(valueDt.isLong && targetDt.isWord) {
|
||||
// long to word, just take the lsw
|
||||
assignCastViaLswFunc(value, target)
|
||||
} else if(valueDt.isPointer && targetDt.isPointer) {
|
||||
// pointer type A to pointer type B, just assign
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
|
||||
} else
|
||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker ${value.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 -> {
|
||||
// This has been handled already earlier on line 961.
|
||||
// // byte to word, just assign to registers first, then assign to variable
|
||||
// assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==BaseDataType.WORD)
|
||||
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
|
||||
// return
|
||||
// }
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// byte to word, just assign to registers first, then assign into array
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt.isSigned)
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null)
|
||||
pointergen.assignWordReg(IndexedPtrTarget(target), RegisterOrPair.AY)
|
||||
else
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
// byte to word or long, just assign to registers
|
||||
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
pointergen.assignByteToWord(PtrTarget(target), value)
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("weird target at ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
if(targetDt.isFloat && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
if(target.register==RegisterOrPair.FAC2)
|
||||
asmgen.pushFAC1()
|
||||
when {
|
||||
valueDt.isUnsignedByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||
asmgen.out(" jsr floats.FREADUY")
|
||||
}
|
||||
valueDt.isSignedByte -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||
asmgen.out(" jsr floats.FREADSA")
|
||||
}
|
||||
valueDt.isUnsignedWord -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||
}
|
||||
valueDt.isSignedWord -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
}
|
||||
valueDt.isLong -> {
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
lda #<$varname
|
||||
ldy #>$varname
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
else -> {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
lda #<cx16.r14
|
||||
ldy #>cx16.r14
|
||||
jsr floats.internal_long_AY_to_FAC""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt at ${target.position}")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
asmgen.out(" jsr floats.MOVEF")
|
||||
asmgen.popFAC1()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(targetDt.isUnsignedWord && valueDt.isPointer) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
|
||||
// No more special optimized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastLongToByte(value: PtExpression, target: AsmAssignTarget) {
|
||||
// long to byte, can't use lsb() because that only works on words
|
||||
when(value) {
|
||||
is PtIdentifier -> {
|
||||
val longvar = asmgen.asmVariableName(value)
|
||||
asmgen.out(" lda $longvar")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
|
||||
}
|
||||
is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}")
|
||||
else -> {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
|
||||
asmgen.out(" lda cx16.r14")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtFunctionCall("lsb", true, true,arrayOf(DataType.UBYTE), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
|
||||
}
|
||||
|
||||
|
||||
internal fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtFunctionCall("lsw", true, true, arrayOf(DataType.UWORD), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb)
|
||||
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
|
||||
}
|
||||
|
||||
|
||||
internal fun assignWordToBool(value: PtExpression, target: AsmAssignTarget) {
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ora P8ZP_SCRATCH_REG
|
||||
beq +
|
||||
lda #1
|
||||
+""")
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
|
||||
|
||||
internal fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: BaseDataType) {
|
||||
|
||||
if(targetDt==BaseDataType.FLOAT)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_FAC1_as_bool_into_a | sta $targetAsmVarName")
|
||||
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta $targetAsmVarName
|
||||
lda floats.FAC_ADDR+3
|
||||
sta $targetAsmVarName+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta $targetAsmVarName+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta $targetAsmVarName+3
|
||||
""".trimIndent())
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun assignTypeCastedIdentifier(targetAsmVarName: String, targetDt: DataType,
|
||||
sourceAsmVarName: String, sourceDt: DataType) {
|
||||
if(sourceDt == targetDt)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
|
||||
when {
|
||||
sourceDt.isUnsignedByte || sourceDt.isBool -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
sta $targetAsmVarName
|
||||
lda #0
|
||||
sta $targetAsmVarName+1
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy $sourceAsmVarName
|
||||
jsr floats.cast_from_ub""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
sourceDt.isSignedByte -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
}
|
||||
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
jsr floats.cast_from_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isUnsignedWord -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
ora $sourceAsmVarName+1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
}
|
||||
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
|
||||
lda #0
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
ldy $sourceAsmVarName+1
|
||||
jsr floats.cast_from_uw""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isSignedWord || sourceDt.isPointer -> {
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> {
|
||||
asmgen.out("""
|
||||
lda $sourceAsmVarName
|
||||
ora $sourceAsmVarName+1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
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
|
||||
ldy #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda $sourceAsmVarName
|
||||
ldy $sourceAsmVarName+1
|
||||
jsr floats.cast_from_w""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isFloat -> {
|
||||
asmgen.out(" lda #<$sourceAsmVarName | ldy #>$sourceAsmVarName")
|
||||
when(targetDt.base) {
|
||||
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_as_bool_into_a | sta $targetAsmVarName")
|
||||
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.cast_as_long""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
sourceDt.isString -> throw AssemblyError("cannot typecast a string value")
|
||||
sourceDt.isLong -> {
|
||||
if(targetDt.isByteOrBool) {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
asmgen.out("""
|
||||
lda #<$sourceAsmVarName
|
||||
ldy #>$sourceAsmVarName
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun assignTypeCastedRegisters(targetAsmVarName: String, targetDt: BaseDataType,
|
||||
regs: RegisterOrPair, sourceDt: BaseDataType, position: Position) {
|
||||
if(sourceDt == targetDt && sourceDt != BaseDataType.POINTER)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
|
||||
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
|
||||
when(sourceDt) {
|
||||
BaseDataType.BOOL -> {
|
||||
if (targetDt.isByteOrBool) asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
else throw AssemblyError("assign bool to non-byte variable")
|
||||
}
|
||||
BaseDataType.UBYTE -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> {
|
||||
val compare = if(regs==RegisterOrPair.A) "cmp" else "cp${regs.toString().lowercase()}"
|
||||
asmgen.out("""
|
||||
$compare #0
|
||||
beq +
|
||||
ld${regs.toString().lowercase()} #1
|
||||
+ st${regs.toString().lowercase()} $targetAsmVarName""")
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
st${regs.toString().lowercase()} $targetAsmVarName
|
||||
lda #0
|
||||
sta $targetAsmVarName+1
|
||||
sta $targetAsmVarName+2
|
||||
sta $targetAsmVarName+3""")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> asmgen.out(" tay")
|
||||
RegisterOrPair.X -> asmgen.out(" txa | tay")
|
||||
RegisterOrPair.Y -> {}
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.cast_from_ub""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign byte to bool $position")
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" txa")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya")
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.signExtendAYlsb(sourceDt)
|
||||
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||
asmgen.signExtendLongVariable(targetAsmVarName, sourceDt)
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" txa")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya")
|
||||
else -> throw AssemblyError("non-byte regs")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy #<$targetAsmVarName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy #>$targetAsmVarName
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
jsr floats.cast_from_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign uword to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.WORD, BaseDataType.POINTER -> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
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.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
if(regs!=RegisterOrPair.AY)
|
||||
throw AssemblyError("only supports AY here")
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
txa
|
||||
jsr floats.cast_from_uw""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
when(targetDt) {
|
||||
BaseDataType.BOOL -> TODO("assign word to bool $position")
|
||||
BaseDataType.BYTE, BaseDataType.UBYTE -> {
|
||||
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||
}
|
||||
BaseDataType.UWORD, BaseDataType.POINTER -> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
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")
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda #<$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$targetAsmVarName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
txa
|
||||
jsr floats.cast_from_w""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
BaseDataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||
BaseDataType.POINTER -> {
|
||||
if(targetDt.isWord || targetDt.isPointer) {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
else -> throw AssemblyError("non-word regs")
|
||||
}
|
||||
} else if(targetDt.isLong) {
|
||||
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.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
|
||||
} else {
|
||||
throw AssemblyError("cannot assign pointer to $targetDt")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
val startreg = regs.startregname()
|
||||
if(targetDt.isByteOrBool) {
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName")
|
||||
} else if(targetDt.isWord || targetDt.isPointer) {
|
||||
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName | lda cx16.$startreg+1 | sta $targetAsmVarName+1")
|
||||
} else if(targetDt.isFloat) {
|
||||
if(regs==RegisterOrPair.R0R1)
|
||||
throw AssemblyError("cannot assign long in R0R1 to float because r1 is used as a parameter $position")
|
||||
asmgen.out("""
|
||||
lda #<cx16.$startreg
|
||||
ldy #>cx16.$startreg
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$targetAsmVarName
|
||||
ldy #>$targetAsmVarName
|
||||
jsr floats.internal_long_R1_to_float_AY""")
|
||||
} else
|
||||
throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
else -> throw AssemblyError("weird type $sourceDt")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute 8-bit offset Y = idx*structSize + fieldOffset for a struct-array element.
|
||||
* Assumes totalBytes <=256 so offset fits in Y. Uses A/Y and multiply_bytes when needed.
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -5,70 +5,81 @@ import io.kotest.matchers.shouldBe
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.codegen.cpu6502.*
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.cpu6502.hasInstr
|
||||
import prog8.codegen.cpu6502.haslabel
|
||||
import prog8.codegen.cpu6502.isBranch
|
||||
import prog8.codegen.cpu6502.isLoadReg
|
||||
import prog8.codegen.cpu6502.isStoreReg
|
||||
import prog8.codegen.cpu6502.isStoreRegOrZero
|
||||
import prog8.codegen.cpu6502.keeplabel
|
||||
import prog8.codegen.cpu6502.modifiesYRegister
|
||||
import prog8.codegen.cpu6502.optimizeAssembly
|
||||
|
||||
|
||||
class TestAsmOptimizer: FunSpec({
|
||||
|
||||
val machine = C64Target()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val program = PtProgram("test", VMTarget())
|
||||
val symbolTable = SymbolTable(program)
|
||||
|
||||
// Add test variables with known non-IO addresses so the optimizer can resolve them
|
||||
// Non-IO addresses in typical C64 RAM range
|
||||
symbolTable.add(StMemVar("myvar", DataType.UBYTE, 0x0300u, null, null))
|
||||
symbolTable.add(StMemVar("var", DataType.UBYTE, 0x0301u, null, null))
|
||||
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0302u, null, null))
|
||||
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0304u, null, null))
|
||||
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0306u, null, null))
|
||||
symbolTable.add(StMemVar("array", DataType.arrayFor(BaseDataType.UBYTE, machine), 0x0302u, 16u, null))
|
||||
symbolTable.add(StMemVar("wordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0310u, 32u, null))
|
||||
symbolTable.add(StMemVar("nswordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0320u, 32u, null))
|
||||
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0400u, null, null))
|
||||
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0402u, null, null))
|
||||
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0404u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_PTR", DataType.UWORD, 0x02u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_W1", DataType.UWORD, 0x04u, null, null))
|
||||
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0310u, null, null))
|
||||
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0312u, null, null))
|
||||
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0410u, null, null))
|
||||
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0412u, null, null))
|
||||
|
||||
fun optimize(lines: MutableList<String>): Int {
|
||||
return optimizeAssembly(lines, machine, symbolTable)
|
||||
fun optimize(lines: MutableList<String>) {
|
||||
optimizeAssembly(lines, machine, symbolTable)
|
||||
}
|
||||
|
||||
// --- optimizeIncDec ---
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence") {
|
||||
val lines = mutableListOf(" iny", " dey", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence with comments") {
|
||||
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes inx+dex sequence") {
|
||||
val lines = mutableListOf(" inx", " dex", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dey+iny sequence") {
|
||||
val lines = mutableListOf(" dey", " iny", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dex+inx sequence") {
|
||||
val lines = mutableListOf(" dex", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: preserves non-canceling sequences") {
|
||||
val lines = mutableListOf(" iny", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" iny", " inx", " rts", " nop")
|
||||
}
|
||||
|
||||
@@ -76,42 +87,37 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X") {
|
||||
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
|
||||
// Note: comments don't affect the optimization, but the optimizer checks lines[1] and lines[2]
|
||||
// and the comment is part of the line, so "sta myvar ; store it" doesn't match "sta myvar"
|
||||
// The optimizer uses substring(4) which includes the comment
|
||||
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes pha + pla") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " pha", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phx + plx") {
|
||||
val lines = mutableListOf(" nop", " phx", " plx", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phy + ply") {
|
||||
val lines = mutableListOf(" nop", " phy", " ply", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes lda X + sta X") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " lda myvar", " rts")
|
||||
}
|
||||
|
||||
@@ -119,77 +125,90 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp") {
|
||||
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+jmp") {
|
||||
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+bra") {
|
||||
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0") {
|
||||
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
|
||||
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes bra+jmp") {
|
||||
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" bra somewhere", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
|
||||
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: three consecutive jsr only tail jsr+rts becomes jmp") {
|
||||
val lines = mutableListOf($$" jsr $4000", $$" jsr $5000", $$" jsr $6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf($$" jsr $4000", $$" jsr $5000", $$" jmp $6000", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: single line with embedded newlines only tail converted defensively") {
|
||||
val lines = mutableListOf($$" jsr $4000\n jsr $5000\n jsr $6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
// Before fix this became jmp/jmp/jmp via replace("jsr","jmp"); after fix only one jmp via replaceMnemonic
|
||||
val jmpCount = lines.joinToString("\n").split("jmp").size - 1
|
||||
jmpCount shouldBe 1
|
||||
}
|
||||
|
||||
// --- optimizeUselessPushPopStack ---
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" tya", " ldy #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes phx+ldx+pla") {
|
||||
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" txa", " ldx #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+lda+pla") {
|
||||
// Note: optimizer removes all three: pha, lda, and pla
|
||||
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
|
||||
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
@@ -197,14 +216,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -212,14 +231,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
|
||||
}
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -231,7 +250,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda #1", " ldy #0", " sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" sta var2", " sty var2+1",
|
||||
@@ -246,11 +265,111 @@ class TestAsmOptimizer: FunSpec({
|
||||
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeSamePointerIndexing: guards on the index reload ---
|
||||
|
||||
fun padded(vararg body: String): MutableList<String> =
|
||||
(body.toList() + List(14 - body.size) { " nop" }).toMutableList()
|
||||
|
||||
/** Same, but one line shorter, for cases where the optimizer is expected to drop a line. */
|
||||
fun paddedAfterDrop(vararg body: String): MutableList<String> =
|
||||
(body.toList() + List(13 - body.size) { " nop" }).toMutableList()
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when the index variable is written in between") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " inc i", " ldy i", " sta arr,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " inc i", " ldy i", " sta arr,y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when the index variable is decremented in between") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " dec i", " ldy i", " sta arr,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " dec i", " ldy i", " sta arr,y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload across a jsr") {
|
||||
val lines = padded(" ldy #0", " lda (ptr),y", " jsr someSub", " ldy #0", " sta (ptr),y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy #0", " lda (ptr),y", " jsr someSub", " ldy #0", " sta (ptr),y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload across a label") {
|
||||
val lines = padded(" ldy #0", " lda (ptr),y", "someLabel:", " ldy #0", " sta (ptr),y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy #0", " lda (ptr),y", "someLabel:", " ldy #0", " sta (ptr),y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when an indirect store may alias the index") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " sta (vptr),y", " ldy i", " lda arr2,y")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " sta (vptr),y", " ldy i", " lda arr2,y")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when an intervening instruction modifies Y") {
|
||||
val lines = padded(" ldy #0", " lda (ptr),y", " tay", " ldy #0", " sta (ptr),y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy #0", " lda (ptr),y", " tay", " ldy #0", " sta (ptr),y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload for an IO index operand") {
|
||||
val lines = padded($$" ldy c64.$d020", " lda arr,y", " lda arr2,y", $$" ldy c64.$d020", " lda arr3,y")
|
||||
optimize(lines)
|
||||
lines shouldBe padded($$" ldy c64.$d020", " lda arr,y", " lda arr2,y", $$" ldy c64.$d020", " lda arr3,y")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: still drops reload when a store to an unrelated var intervenes") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " sta other", " ldy i", " sta arr,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe paddedAfterDrop(" ldy i", " lda arr,y", " sta other", " sta arr,y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when the two ldy load DIFFERENT immediates") {
|
||||
// regression: seed[1] then seed[0] - the reload selects a different element
|
||||
val lines = padded(" ldy #1", " lda seed_msb,y", " sta x", " ldy #0", " lda seed_msb,y", " sta y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy #1", " lda seed_msb,y", " sta x", " ldy #0", " lda seed_msb,y", " sta y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when the two ldy load different variables") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " sta x", " ldy j", " lda arr,y", " sta y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " sta x", " ldy j", " lda arr,y", " sta y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: KEEPS reload when the two ldy differ by an offset") {
|
||||
val lines = padded(" ldy w+0", " lda arr,y", " sta x", " ldy w+1", " lda arr,y", " sta y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy w+0", " lda arr,y", " sta x", " ldy w+1", " lda arr,y", " sta y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: matched load/store arm also requires the same index") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " clc", " ldy j", " sta arr,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " clc", " ldy j", " sta arr,y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: relaxed arm drops reload for two indexed loads") {
|
||||
val lines = padded(" ldy sp", " lda cx,y", " sta cxv", " ldy sp", " lda cy,y", " sta cyv", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe paddedAfterDrop(" ldy sp", " lda cx,y", " sta cxv", " lda cy,y", " sta cyv", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: relaxed arm does not fire when Y is modified") {
|
||||
val lines = padded(" ldy sp", " lda cx,y", " dey", " ldy sp", " lda cy,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy sp", " lda cx,y", " dey", " ldy sp", " lda cy,y", " rts")
|
||||
}
|
||||
|
||||
test("samePointerIndexing: relaxed arm keeps reload when index is written in between") {
|
||||
val lines = padded(" ldy i", " lda arr,y", " inc i", " ldy i", " lda arr2,y", " rts")
|
||||
optimize(lines)
|
||||
lines shouldBe padded(" ldy i", " lda arr,y", " inc i", " ldy i", " lda arr2,y", " rts")
|
||||
}
|
||||
|
||||
// --- optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore ---
|
||||
|
||||
test("optimizeAddWordToSameVariable: optimizes P8ZP_SCRATCH_PTR += AY") {
|
||||
@@ -260,7 +379,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
|
||||
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
|
||||
@@ -271,28 +390,201 @@ class TestAsmOptimizer: FunSpec({
|
||||
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
|
||||
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffset (byte arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffset: folds byte array lda+clc+adc+tay+lda to ldy+lda") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " lda array+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: folds with x register") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #5", " tax", " lda array,x",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldx var"
|
||||
lines[1] shouldBe " lda array+5,x"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: works with sta instead of lda") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #1", " tay", " sta array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " sta array+1,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: does not fold indirect indexed (ptr),y") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " lda (ptr),y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: preserves hex constant format") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #$0f", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " lda array+$0f,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: does not fold when 5th op is not absolute indexed") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #3", " tay", " nop",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: works with cmp instruction") {
|
||||
val lines = mutableListOf(
|
||||
" lda myvar", " clc", " adc #7", " tay", " cmp array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy myvar"
|
||||
lines[1] shouldBe " cmp array+7,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffset: preserves label on first instruction") {
|
||||
val lines = mutableListOf(
|
||||
"mylabel: lda myvar", " clc", " adc #3", " tay", " lda array,y",
|
||||
" rts", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe "mylabel: ldy myvar"
|
||||
lines[1] shouldBe " lda array+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffsetSplitWord (split word arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with y") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx wordarray_msb,y",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldy var"
|
||||
lines[1] shouldBe " lda wordarray_lsb+3,y"
|
||||
lines[2] shouldBe " ldx wordarray_msb+3,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with x") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tax",
|
||||
" lda wordarray_lsb,x", " ldx wordarray_msb,x",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " ldx var"
|
||||
lines[1] shouldBe " lda wordarray_lsb+3,x"
|
||||
lines[2] shouldBe " ldx wordarray_msb+3,x"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: does not fold mismatched registers") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx wordarray_msb,x",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetSplitWord: does not fold non-lsb-msb pair") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda wordarray_lsb,y", " ldx other,y",
|
||||
" rts", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 10
|
||||
}
|
||||
|
||||
// --- optimizeFoldIndexOffsetNonSplitWord (non-split word arrays) ---
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: folds non-split word array access") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " asl a", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[0] shouldBe " lda var"
|
||||
lines[1] shouldBe " asl a"
|
||||
lines[2] shouldBe " tay"
|
||||
lines[3] shouldBe " lda nswordarray+6,y"
|
||||
lines[4] shouldBe " ldx nswordarray+1+6,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: scales hex constants") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #$02", " asl a", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines[3] shouldBe " lda nswordarray+$04,y"
|
||||
lines[4] shouldBe " ldx nswordarray+1+$04,y"
|
||||
lines.size shouldBe 7
|
||||
}
|
||||
|
||||
test("optimizeFoldIndexOffsetNonSplitWord: does not fold without asl a") {
|
||||
val lines = mutableListOf(
|
||||
" lda var", " clc", " adc #3", " tay",
|
||||
" lda nswordarray,y", " ldx nswordarray+1,y",
|
||||
" rts", " nop", " nop"
|
||||
)
|
||||
optimize(lines)
|
||||
lines.size shouldBe 9
|
||||
}
|
||||
|
||||
// --- Edge cases ---
|
||||
|
||||
test("handles empty input") {
|
||||
val lines = mutableListOf<String>()
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("handles single line input") {
|
||||
val lines = mutableListOf(" rts")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("multiple optimizations in one pass") {
|
||||
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
|
||||
optimize(lines) shouldBe 2
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #1", " sta var", " rts")
|
||||
}
|
||||
|
||||
@@ -547,7 +839,9 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should be removed since "ora" doesn't modify Y
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora myvar", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("actual Y-modifying instructions should still be detected") {
|
||||
@@ -557,7 +851,11 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should NOT be removed since "tay" modifies Y
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" ldy #0", " lda (ptr),y", " tay",
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
|
||||
@@ -584,8 +882,13 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
val count = optimize(lines)
|
||||
count shouldBe 0
|
||||
optimize(lines)
|
||||
lines.size shouldBe 8 // all instructions preserved
|
||||
lines shouldBe listOf(
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -12,10 +12,9 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.cpu6502.AsmGen6502
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
@@ -46,13 +45,15 @@ class TestCodegen: FunSpec({
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = AsmGen6502(prefixSymbols = false, 0)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val compTarget = C64Target()
|
||||
val codegen = AsmGen6502(0)
|
||||
val program = PtProgram("test", VMTarget())
|
||||
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
sub.add(PtVariable(
|
||||
"pi",
|
||||
DataType.UBYTE,
|
||||
false,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
@@ -62,7 +63,8 @@ class TestCodegen: FunSpec({
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleX",
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
DataType.arrayFor(BaseDataType.UBYTE, compTarget),
|
||||
false,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
@@ -72,7 +74,8 @@ class TestCodegen: FunSpec({
|
||||
))
|
||||
sub.add(PtVariable(
|
||||
"particleDX",
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
DataType.arrayFor(BaseDataType.UBYTE, compTarget),
|
||||
false,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
@@ -83,6 +86,7 @@ class TestCodegen: FunSpec({
|
||||
sub.add(PtVariable(
|
||||
"xx",
|
||||
DataType.WORD,
|
||||
false,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
@@ -93,14 +97,14 @@ class TestCodegen: FunSpec({
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(false, Position.DUMMY).also {
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, false, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE, compTarget), Position.DUMMY))
|
||||
idx.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
|
||||
}
|
||||
it.add(targetIdx)
|
||||
}
|
||||
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||
value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
|
||||
val value = PtArrayIndexer(DataType.UBYTE, false, Position.DUMMY)
|
||||
value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE, compTarget), Position.DUMMY))
|
||||
value.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
|
||||
assign.add(target)
|
||||
assign.add(value)
|
||||
@@ -166,11 +170,11 @@ class TestCodegen: FunSpec({
|
||||
}
|
||||
|
||||
test("memory mapped zp var is correctly considered to be zp var") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val program = PtProgram("test", VMTarget())
|
||||
val st = SymbolTable(program)
|
||||
st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null))
|
||||
st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null))
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests())
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests(), program)
|
||||
allocator.isZpVar("zpmemvar") shouldBe true
|
||||
allocator.isZpVar("normalmemvar") shouldBe false
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,46 +0,0 @@
|
||||
package prog8.codegen.experimental
|
||||
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irProgram = irCodeGen.generate()
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
// this stub only writes the IR program to disk but doesn't generate anything else.
|
||||
IRFileWriter(irProgram, null).write()
|
||||
|
||||
if(!options.quiet)
|
||||
println("** experimental codegen stub: no assembly generated **")
|
||||
return EmptyProgram
|
||||
}
|
||||
}
|
||||
|
||||
private object EmptyProgram : IAssemblyProgram {
|
||||
override val name = "<Empty Program>"
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
if(!options.quiet)
|
||||
println("** nothing assembled **")
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,5 +11,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -81,7 +81,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.push(IRDataType.BYTE, tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.push(IRDataType.WORD, tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.push(IRDataType.LONG, tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -105,36 +105,36 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
addInstr(result, IRInstructions.push(IRDataType.FLOAT, tr.resultFpReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPop(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null)
|
||||
addInstr(result, IRInstructions.pop(IRDataType.BYTE, resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopW(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
addInstr(result, IRInstructions.pop(IRDataType.WORD, resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopL(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
addInstr(result, IRInstructions.pop(IRDataType.LONG, resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.LONG, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopF(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
|
||||
addInstr(result, IRInstructions.pop(IRDataType.FLOAT, resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultRegister)
|
||||
}
|
||||
|
||||
private fun funcCopyFromPointer1ToPointer2(call: PtFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
@@ -147,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
IRDataType.LONG -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0)
|
||||
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
|
||||
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
|
||||
}
|
||||
}
|
||||
IRDataType.FLOAT -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0)
|
||||
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
|
||||
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid type $type")
|
||||
@@ -169,12 +169,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
return if(resultType==IRDataType.FLOAT) {
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultFpReg, valueTr.resultFpReg), null)
|
||||
ExpressionCodeResult(result, resultType, -1, resultFpReg)
|
||||
}
|
||||
else {
|
||||
val resultReg = codeGen.registers.next(resultType)
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultReg, valueTr.resultReg), null)
|
||||
ExpressionCodeResult(result, resultType, resultReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -183,12 +183,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
|
||||
addInstr(result, IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(addressTr.resultReg)))), null)
|
||||
return if(call.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else {
|
||||
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
|
||||
val slot = if(codeGen.options.compTarget.cpu.usesM68kConvention) 10 else 4
|
||||
addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, resultvalueReg, CallingConventionSlot(slot)), null)
|
||||
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -201,8 +202,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, bankTr, bankTr.resultReg, -1)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult {
|
||||
@@ -219,15 +221,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, argumentX, argumentX.resultReg, -1)
|
||||
addToResult(result, argumentY, argumentY.resultReg, -1)
|
||||
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg,
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg,
|
||||
IRDataType.BYTE to argumentA.resultReg,
|
||||
IRDataType.BYTE to argumentX.resultReg,
|
||||
IRDataType.BYTE to argumentY.resultReg,
|
||||
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
|
||||
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult {
|
||||
if(signed && codeGen.options.compTarget.cpu.is6502) {
|
||||
codeGen.errors.err("expected all ubyte or all uword arguments (no signed divmod support on 6502 yet)", call.position)
|
||||
return ExpressionCodeResult(mutableListOf(), type, -1, -1)
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val number = call.args[0]
|
||||
val divident = call.args[1]
|
||||
@@ -238,33 +245,56 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if(divident is PtNumber) {
|
||||
val tr = exprGen.translateExpression(number)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
|
||||
divisionReg = tr.resultReg
|
||||
remainderReg = codeGen.registers.next(type)
|
||||
addInstr(result, IRInstructions.divmodImmediate(divmodOpcode, type, tr.resultReg, remainderReg, divident.number.toInt()), null)
|
||||
divisionReg = tr.resultReg
|
||||
} else {
|
||||
val numTr = exprGen.translateExpression(number)
|
||||
addToResult(result, numTr, numTr.resultReg, -1)
|
||||
val dividentTr = exprGen.translateExpression(divident)
|
||||
addToResult(result, dividentTr, dividentTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
|
||||
divisionReg = numTr.resultReg
|
||||
remainderReg = dividentTr.resultReg
|
||||
addInstr(result, IRInstructions.divmodRegister(divmodrOpcode, type, numTr.resultReg, dividentTr.resultReg), null)
|
||||
divisionReg = numTr.resultReg
|
||||
}
|
||||
// DIVMOD instruction result convention: on value stack, division and remainder on top.
|
||||
addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null)
|
||||
// return the 2 result values: division and remainder division in AY, remainder in R15
|
||||
if(type==IRDataType.BYTE) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
// DIVMOD result convention: quotient in reg1, remainder in reg2.
|
||||
// Route to the return value locations expected by the caller (via returnsWhatWhere).
|
||||
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
|
||||
// m68k: first return value in D0 (slot 10), second in D1 (slot 11)
|
||||
when (type) {
|
||||
IRDataType.BYTE -> {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11))
|
||||
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10))
|
||||
}
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11))
|
||||
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10))
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid type for DIVMOD")
|
||||
}
|
||||
} else if(type==IRDataType.WORD) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
} else {
|
||||
// 6502/cx16: quotient to A (slot 0) or AY (slot 4), remainder to cx16.r15
|
||||
// Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it.
|
||||
when (type) {
|
||||
IRDataType.BYTE -> {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15"))
|
||||
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0))
|
||||
}
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15"))
|
||||
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4))
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid type for DIVMOD")
|
||||
}
|
||||
} else throw AssemblyError("invalid type for DIVMOD")
|
||||
}
|
||||
return ExpressionCodeResult(result, type, -1, -1)
|
||||
}
|
||||
|
||||
@@ -275,7 +305,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, left, left.resultReg, -1)
|
||||
addToResult(result, right, right.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to resultReg)
|
||||
val addressDt = codeGen.addressDt
|
||||
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(addressDt to left.resultReg, addressDt to right.resultReg), IRDataType.BYTE to resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
@@ -285,9 +316,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val rightTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||
val dt = irType(call.args[0].type)
|
||||
val dt = codeGen.irType(call.args[0].type)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||
it += IRInstructions.compare(dt, leftTr.resultReg, rightTr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
|
||||
}
|
||||
@@ -300,14 +331,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
when (sourceDt.base) {
|
||||
BaseDataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
it += IRInstructions.move(IRDataType.BYTE, compareReg, tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstructions.compareImmediate(IRDataType.BYTE, compareReg, 0)
|
||||
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
|
||||
it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, tr.resultReg)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
@@ -316,9 +350,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||
it += IRInstructions.move(IRDataType.WORD, compareReg, tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstructions.compareImmediate(IRDataType.WORD, compareReg, 0)
|
||||
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
|
||||
it += IRInstructions.unary(Opcode.NEG, IRDataType.WORD, tr.resultReg)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
|
||||
@@ -327,18 +363,24 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
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)
|
||||
it += IRInstructions.move(IRDataType.LONG, compareReg, tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstructions.compareImmediate(IRDataType.LONG, compareReg, 0)
|
||||
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
|
||||
it += IRInstructions.unary(Opcode.NEG, IRDataType.LONG, 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 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.FABS, IRDataType.FLOAT, resultFpReg, tr.resultFpReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
// pointer is unsigned, abs has no effect
|
||||
return ExpressionCodeResult(result, IRDataType.POINTER, tr.resultReg, -1)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
@@ -351,12 +393,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if(tr.dt==IRDataType.FLOAT) {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg))
|
||||
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultFpReg)
|
||||
}
|
||||
} else {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, reg2 = tr.resultReg)
|
||||
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultReg)
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
@@ -371,7 +413,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg)
|
||||
it += IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, resultReg, tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -379,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
|
||||
it += IRInstructions.binary(Opcode.SQRT, IRDataType.WORD, resultReg, tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -387,7 +429,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD) // sqrt of a long still produces just a word result
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.LONG, reg1=resultReg, reg2=tr.resultReg)
|
||||
it += IRInstructions.binary(Opcode.SQRT, IRDataType.LONG, resultReg, tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
@@ -395,7 +437,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg))
|
||||
it += IRInstructions.binary(Opcode.SQRT, IRDataType.FLOAT, resultFpReg, tr.resultFpReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
@@ -410,13 +452,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
// msb is 0, use EXT
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
|
||||
} else {
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
|
||||
addInstr(result, IRInstructions.concat(IRDataType.BYTE, resultReg, msbTr.resultReg, lsbTr.resultReg), null)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
@@ -430,23 +472,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
// 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)
|
||||
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, resultReg, 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)
|
||||
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswTr.resultReg, 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
|
||||
// use EXTL.b: zero-extend byte directly to long
|
||||
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)
|
||||
addInstr(result, IRInstructions.binary(Opcode.EXTL, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
|
||||
} else {
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
val b2Tr = exprGen.translateExpression(call.args[1])
|
||||
@@ -458,9 +498,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
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)
|
||||
addInstr(result, IRInstructions.concat(IRDataType.BYTE, mswReg, msbTr.resultReg, b2Tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.concat(IRDataType.BYTE, lswReg, b1Tr.resultReg, lsbTr.resultReg), null)
|
||||
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswReg, lswReg), null)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -468,44 +508,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val type = codeGen.irType(call.type)
|
||||
require(type != IRDataType.FLOAT)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val type = irType(call.type)
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
val minimumTr = exprGen.translateExpression(call.args[1])
|
||||
val maximumTr = exprGen.translateExpression(call.args[2])
|
||||
result += valueTr.chunks
|
||||
result += minimumTr.chunks
|
||||
result += maximumTr.chunks
|
||||
if(type==IRDataType.FLOAT) {
|
||||
result += codeGen.makeSyscall(
|
||||
IMSyscall.CLAMP_FLOAT, listOf(
|
||||
valueTr.dt to valueTr.resultFpReg,
|
||||
minimumTr.dt to minimumTr.resultFpReg,
|
||||
maximumTr.dt to maximumTr.resultFpReg,
|
||||
), type to valueTr.resultFpReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, -1, valueTr.resultFpReg)
|
||||
} else {
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
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(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
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")
|
||||
}
|
||||
val resultReg = codeGen.registers.next(type)
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
val type = codeGen.irType(call.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
@@ -514,17 +545,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
|
||||
val after = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(comparisonOpcode, type, reg1 = rightTr.resultReg, reg2 = leftTr.resultReg, labelSymbol = after)
|
||||
it += IRInstructions.branchRegister(comparisonOpcode, type, rightTr.resultReg, leftTr.resultReg, codeLabel(after))
|
||||
// right <= left, take right
|
||||
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
|
||||
it += IRInstructions.jump(codeLabel(after))
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMax(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
val type = codeGen.irType(call.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
@@ -533,33 +564,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
|
||||
val after = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(comparisonOpcode, type, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg, labelSymbol = after)
|
||||
it += IRInstructions.branchRegister(comparisonOpcode, type, leftTr.resultReg, rightTr.resultReg, codeLabel(after))
|
||||
// right >= left, take right
|
||||
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
|
||||
it += IRInstructions.jump(codeLabel(after))
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
// Try struct-array field folding: pokew(&arr[idx]+fieldOff, value) -> STOREX arr+fieldOff,S=structSize
|
||||
tryFoldStructArrayPoke(call, dt)?.let { return it }
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress())
|
||||
it += IRInstructions.storeZero(Opcode.STOREZM, dt, IRMemory.direct(address.toAddress()))
|
||||
}
|
||||
} else {
|
||||
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
|
||||
if(address!=null) {
|
||||
val tr = exprGen.translateExpression(address)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null)
|
||||
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg, offset!!)), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null)
|
||||
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg)), null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -568,9 +601,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultFpReg, IRMemory.direct(address.toAddress())), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultReg, IRMemory.direct(address.toAddress())), null)
|
||||
}
|
||||
} else {
|
||||
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
|
||||
@@ -579,18 +612,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
|
||||
}
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg)), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg)), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,26 +637,26 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if(call.args[0] is PtNumber) {
|
||||
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(address.toAddress())), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpReg, IRMemory.indirect(tr.resultReg)), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
} else {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val resultRegister = codeGen.registers.next(dt)
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, dt, resultRegister, IRMemory.direct(address.toAddress())), null)
|
||||
ExpressionCodeResult(result, dt, resultRegister, -1)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(dt)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, dt, resultReg, IRMemory.indirect(tr.resultReg)), null)
|
||||
ExpressionCodeResult(result, dt, resultReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -640,11 +673,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val addressNum = address.number.toUInt()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct(addressNum.toAddress())), null)
|
||||
if (newValueTr == null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null)
|
||||
addInstr(result, IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct(addressNum.toAddress())), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newValueTr.resultReg, IRMemory.direct(addressNum.toAddress())), null)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
@@ -657,11 +690,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
|
||||
if (newValueTr == null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, newValueTr.resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -670,10 +703,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcStructAlloc(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
val resultReg = codeGen.registers.next(IRDataType.POINTER)
|
||||
val labelname = SymbolTable.labelnameForStructInstance(call)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
|
||||
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
code += IRInstructions.loadAddress(IRDataType.POINTER, resultReg, "${StStructInstanceBlockName}.$labelname")
|
||||
return ExpressionCodeResult(code, IRDataType.POINTER, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcLsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
|
||||
@@ -682,9 +715,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
if(fromLong)
|
||||
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -694,14 +727,25 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
// TODO this can be more optimal if the argument is a variable or memory address
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
// low byte returned in A, mid in R15, high (bank) in R14
|
||||
val byteReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null)
|
||||
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
|
||||
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1 = byteReg), null)
|
||||
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
|
||||
// m68k: 3 return values in D0 (slot 10), D1 (slot 11), D2 (slot 12)
|
||||
// store high byte first, then mid, then low (to avoid clobbering)
|
||||
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(12)), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(11)), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(10)), null)
|
||||
} else {
|
||||
// 6502/cx16: low byte in A (slot 0), mid in cx16.r15, high (bank) in cx16.r14
|
||||
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r14")), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r15")), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(0)), null)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -710,7 +754,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
@@ -720,9 +764,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
if(fromLong)
|
||||
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -732,14 +776,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcRolRor(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val arg = call.args[0]
|
||||
val vmDt = irType(arg.type)
|
||||
val vmDt = codeGen.irType(arg.type)
|
||||
val opcodeMemAndReg = when(call.name) {
|
||||
"rol" -> Opcode.ROXLM to Opcode.ROXL
|
||||
"ror" -> Opcode.ROXRM to Opcode.ROXR
|
||||
@@ -750,39 +794,37 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val ident = arg as? PtIdentifier
|
||||
if(ident!=null) {
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = ident.name), null)
|
||||
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(ident.name)), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt()
|
||||
if(memAddr!=null) {
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null)
|
||||
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(memAddr.toAddress())), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
val arr = (arg as? PtArrayIndexer)
|
||||
val index = arr?.index?.asConstInteger()
|
||||
if(arr!=null && index!=null) {
|
||||
if(arr.variable==null)
|
||||
TODO("support for ptr indexing ${arr.position}")
|
||||
if(arr!=null && index!=null && arr.variable!=null) {
|
||||
val variable = arr.variable!!.name
|
||||
if(arr.splitWords) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
when(opcodeMemAndReg.first) {
|
||||
Opcode.ROXRM, Opcode.RORM -> {
|
||||
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
|
||||
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
|
||||
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
|
||||
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
|
||||
}
|
||||
Opcode.ROXLM, Opcode.ROLM -> {
|
||||
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
|
||||
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
|
||||
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
|
||||
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
|
||||
}
|
||||
else -> throw AssemblyError("wrong rol/ror opcode")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val offset = codeGen.program.memsizer.memorySize(arr.type, index)
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null)
|
||||
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(variable, offset)), null)
|
||||
}
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
@@ -790,17 +832,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val opcode = opcodeMemAndReg.second
|
||||
val saveCarry = opcode in OpcodesThatDependOnCarry && !arg.isSimple()
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
|
||||
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
|
||||
val tr = exprGen.translateExpression(arg)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.POPST), null)
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
|
||||
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
|
||||
addInstr(result, IRInstructions.unary(opcode, vmDt, tr.resultReg), null)
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
|
||||
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
|
||||
result += codeGen.assignRegisterTo(arg, tr.resultReg)
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.POPST), null)
|
||||
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
@@ -808,60 +850,92 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val target = call.args[0]
|
||||
val isConstZeroValue = call.args[1].asConstInteger()==0
|
||||
val isBigEndian = codeGen.options.compTarget.cpu.isBigEndian
|
||||
fun byteOffset(elementSize: Int) = if(msb != isBigEndian) elementSize - 1 else 0
|
||||
when(target) {
|
||||
is PtIdentifier -> {
|
||||
val offset = if (msb) { if(target.type.isLong) 3 else 1 } else 0
|
||||
val offset = byteOffset(if(target.type.isLong) 4 else 2)
|
||||
if(isConstZeroValue) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset)
|
||||
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
|
||||
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
|
||||
it += IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerReg, offset))
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset)
|
||||
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
|
||||
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
|
||||
it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerReg, offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
if(target.splitWords) {
|
||||
if(target.variable==null) {
|
||||
// pointer-based array indexing: compute memory address and store byte
|
||||
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
|
||||
val pointerTr = exprGen.translateExpression(target.pointerderef!!)
|
||||
addToResult(result, pointerTr, pointerTr.resultReg, -1)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
val elementByteOffset = byteOffset(eltSize)
|
||||
if(constIndex != null) {
|
||||
val offset = eltSize * constIndex + elementByteOffset
|
||||
if(offset > 0)
|
||||
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, offset), null)
|
||||
} else {
|
||||
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, true)
|
||||
result += code
|
||||
if(eltSize!=1)
|
||||
result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
|
||||
addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerTr.resultReg, indexWordReg), null)
|
||||
if(elementByteOffset > 0)
|
||||
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, elementByteOffset), null)
|
||||
}
|
||||
if(isConstZeroValue) {
|
||||
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerTr.resultReg)), null)
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerTr.resultReg)), null)
|
||||
}
|
||||
}
|
||||
else if(target.splitWords) {
|
||||
// lsb/msb in split arrays, element index 'size' is always 1
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(target.variable==null)
|
||||
TODO("support for ptr indexing ${target.position}")
|
||||
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val idxType = codeGen.options.compTarget.indexRegType
|
||||
val offsetReg = codeGen.registers.next(idxType)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
|
||||
it += IRInstructions.load(idxType, offsetReg, constIndex)
|
||||
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
|
||||
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val idxType = codeGen.options.compTarget.indexRegType
|
||||
val offsetReg = codeGen.registers.next(idxType)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
|
||||
it += IRInstructions.load(idxType, offsetReg, constIndex)
|
||||
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
|
||||
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -873,50 +947,36 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
|
||||
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + byteOffset(eltSize)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
|
||||
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
|
||||
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
val (code, indexReg) = codeGen.loadIndexReg(target.index, codeGen.wordArrayIndex)
|
||||
result += code
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
|
||||
if(msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
|
||||
val offset = byteOffset(eltSize)
|
||||
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
|
||||
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
|
||||
val offset = eltSize*constIndex + byteOffset(eltSize)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
|
||||
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
|
||||
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
val (code, indexReg) = codeGen.loadIndexReg(target.index, codeGen.wordArrayIndex)
|
||||
result += code
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
|
||||
if(msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
|
||||
val offset = byteOffset(eltSize)
|
||||
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -926,4 +986,31 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
|
||||
private fun tryFoldStructArrayPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult? {
|
||||
// Detect pokew(&arr[idx]+fieldOff, value) where arr is struct array with variable idx
|
||||
// and fold to single STOREX/STOREZX with scale=structSize.
|
||||
val info = extractStructArrayIndexInfo(call.args[0], codeGen) ?: return null
|
||||
val isZero = codeGen.isZero(call.args[1])
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val (idxCode, indexReg) = codeGen.loadIndexReg(info.idxExpr, codeGen.wordArrayIndex)
|
||||
result += idxCode
|
||||
return if(isZero) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstructions.storeZero(Opcode.STOREZX, dt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += if(dt==IRDataType.FLOAT) {
|
||||
IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
|
||||
} else {
|
||||
IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
|
||||
}
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,36 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.intermediate.*
|
||||
|
||||
/*
|
||||
Construction helpers for the few IR opcodes that have no dedicated IRInstructions factory,
|
||||
plus a small operand-inspection helper used by the optimizers.
|
||||
|
||||
The structured IRInstructions factory covers almost every opcode, but three opcodes have an
|
||||
operand shape that no named factory produces (a lone source register, or a source register plus
|
||||
an immediate without a destination). They are built here from schema-derived operands, so this
|
||||
module never constructs IRInstruction directly.
|
||||
*/
|
||||
|
||||
/** STOREHFACZERO / STOREHFACONE: a single source float register (no dedicated factory exists). */
|
||||
internal fun storeHwFac(opcode: Opcode, fpRegister: Int): IRInstruction =
|
||||
IRInstructions.push(IRDataType.FLOAT, fpRegister).copy(opcode = opcode)
|
||||
|
||||
/** BITTST: a source register plus a byte immediate bit number, with no destination register. */
|
||||
internal fun bitTest(type: IRDataType, register: Int, bit: Int): IRInstruction =
|
||||
IRInstructions.simple(Opcode.NOP).copy(
|
||||
opcode = Opcode.BITTST,
|
||||
type = type,
|
||||
srcA = IRInstructions.operandFor(Opcode.BITTST, type, InstructionSlot.SRC_A, register),
|
||||
immediate = IRInstructions.immediateFor(Opcode.BITTST, type, bit)
|
||||
)
|
||||
|
||||
/**
|
||||
* The name of the single symbol this instruction refers to, if any.
|
||||
* That is either the symbol a memory reference is based on, the symbol whose address is taken
|
||||
* in an immediate operand, or the label a branch/jump/call transfers control to.
|
||||
*/
|
||||
internal fun IRInstruction.referencedSymbol(): String? =
|
||||
memory?.symbolName
|
||||
?: (immediate as? ImmediateOperand.SymbolAddress)?.symbol
|
||||
?: labelTarget
|
||||
Loaded 100 of 744 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user