mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2520db872 | ||
|
|
ebe26d0cf9 | ||
|
|
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 | ||
|
|
8df2bb8d2e | ||
|
|
c8c51245af | ||
|
|
02110de844 | ||
|
|
7d77a0eca6 | ||
|
|
e6e4714b02 | ||
|
|
d83ece397d | ||
|
|
a3c768bc6b | ||
|
|
a3d14003cb | ||
|
|
8c92663824 | ||
|
|
10d86eb4d8 | ||
|
|
ee5e734f23 | ||
|
|
6b233b7da3 | ||
|
|
a140519374 | ||
|
|
2b118f2e42 | ||
|
|
be9d9fe377 | ||
|
|
d1345aafec | ||
|
|
d5932a7a57 | ||
|
|
5751beb72f | ||
|
|
02194a735a | ||
|
|
b5fc25b2f4 | ||
|
|
63c425f247 | ||
|
|
69261c51b3 | ||
|
|
63d7ef7105 | ||
|
|
88a3e5b23f | ||
|
|
1b5efe1e5d | ||
|
|
2b5a15a178 | ||
|
|
a9f22d9375 | ||
|
|
f9b3514f2a | ||
|
|
be12fb507e | ||
|
|
b2dcd02ee4 | ||
|
|
5c23471254 | ||
|
|
67e284b680 | ||
|
|
1e5ba48fe0 | ||
|
|
8e6a27d7c1 | ||
|
|
ccd3d94ec0 | ||
|
|
98355d3850 | ||
|
|
0e53113b85 | ||
|
|
c55f5e4b50 | ||
|
|
532bf13057 | ||
|
|
de706e79de | ||
|
|
a19f337ebe | ||
|
|
041a904e44 | ||
|
|
97fb5298a8 | ||
|
|
b580a177d4 | ||
|
|
a0b1ca5d9f | ||
|
|
3a291c56b4 | ||
|
|
ed41d5dbc5 | ||
|
|
628bd41740 | ||
|
|
de988f72c6 | ||
|
|
869c5ef544 | ||
|
|
17ee233e8b |
No files matched your search
@@ -1,277 +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.
|
||||
|
||||
## Git Operations
|
||||
- When moving, renaming, or deleting git-tracked files, **always use `git mv` or `git rm`** instead of plain `mv`/`rm`. This preserves history and properly stages the change. Plain `mv`/`rm` causes git to see them as delete+add (losing history).
|
||||
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.
|
||||
|
||||
## 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.
|
||||
## Syntax
|
||||
|
||||
## 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`)
|
||||
- **Symbol aliases**: inside a `.proc`, use `_name = P8ZP_SCRATCH_B1` to give a scratch variable a descriptive name.
|
||||
- **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`, `.fill` (allocate space)
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
- **Number Literals**: Hex `$1234`, Binary `%10101010`, Decimal `123`.
|
||||
- Zero-page variables defined with `=`
|
||||
- `.text` for inline string data
|
||||
- **Conditional assembly**: `.if`, `.elsif`, `.else`, `.endif`
|
||||
- **Memory Sections**: `.section <name>`, `.send <name>` (common sections: `CODE`, `DATA`, `BSS`, `BSS_NOCLEAR`)
|
||||
- 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`.
|
||||
|
||||
## Instructions
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr` (and `inc`, `dec` on 65C02) require an explicit operand for accumulator**: write `rol a`, `inc a`, etc., not just `rol` or `inc`.
|
||||
- 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
|
||||
## Anonymous Labels
|
||||
|
||||
### Instruction Side Effects (Flags)
|
||||
- **`Z` (Zero)**: Set if the result of an operation is 0.
|
||||
- **`N` (Negative)**: Set if bit 7 of the result is 1.
|
||||
- **`C` (Carry)**: Used for unsigned overflow and shifts. `cmp` sets `C` if `Register >= Operand`.
|
||||
- **`V` (Overflow)**: Set if a signed arithmetic operation overflowed.
|
||||
- **Commonly affected by**: `lda`, `ldx`, `ldy`, `inx`, `dex`, `tax`, `tay`, `txa`, `tya`, `and`, `ora`, `eor`, `asl`, `lsr`, `rol`, `ror`, `adc`, `sbc`, `cmp`, `cpx`, `cpy`, `bit`.
|
||||
- **Note**: `lda`, `ldx`, `ldy` do NOT affect the Carry flag. Only `adc`, `sbc`, `cmp`, and shift/rotate instructions affect Carry.
|
||||
Anonymous label definitions are always a single `+` or `-` in column 1.
|
||||
References use repeated signs:
|
||||
|
||||
## 6502 vs 65C02
|
||||
- **CX16 target only**: can use WDC 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `inc a`, `dec a`, `bit #imm`, `bit zp,x`, `bit abs,x`, `jmp (abs,x)`, and `(zp)` indirect addressing mode (e.g., `lda (zp)`).
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra` etc.
|
||||
- **Note**: The Rockwell/bit-manipulation instructions (`rmb`, `smb`, `bbr`, `bbs`) are **NOT** available.
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference table**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=4 (provides exact instruction details for all opcodes: operation, addressing modes, byte length, and cycle count).
|
||||
- `+` 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.
|
||||
|
||||
## 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
|
||||
```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
|
||||
```
|
||||
|
||||
## Assembly within Prog8 Programs
|
||||
## CPU Differences
|
||||
|
||||
### `%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
|
||||
- `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`.
|
||||
|
||||
### `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
|
||||
- **CRITICAL**: Parameter names in `asmsub` are **documentation only**. You MUST use the actual registers in your assembly code, NOT the parameter names (unless you create aliases yourself).
|
||||
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
|
||||
- Accessing Prog8 parameters if they were NOT mapped to registers: use `p8v_paramname`. (Mapping to registers is preferred for speed).
|
||||
## 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
|
||||
|
||||
### Common Branch Logic (Comparisons)
|
||||
| Logic | Unsigned | Signed |
|
||||
|-------|----------|--------|
|
||||
| `A == imm` | `cmp #imm`, `beq label` | (Same) |
|
||||
| `A != imm` | `cmp #imm`, `bne label` | (Same) |
|
||||
| `A < imm` | `cmp #imm`, `bcc label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bmi label` |
|
||||
| `A >= imm` | `cmp #imm`, `bcs label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bpl label` |
|
||||
| `A <= imm` | `beq label`, `bcc label` | (Use complex signed logic or reorder) |
|
||||
| `A > imm` | `beq +`, `bcs label`, `+` | (Use complex signed logic or reorder) |
|
||||
|
||||
### Looping (downto with BNE)
|
||||
```asm
|
||||
ldx #count
|
||||
loop ; do work here
|
||||
dex
|
||||
bne loop ; loop while X != 0 (runs 'count' times)
|
||||
```
|
||||
|
||||
### Self-Modifying Code (SMC) Detection
|
||||
Look for `sta`, `stx`, or `sty` pointing into code labels:
|
||||
```asm
|
||||
lda #$42
|
||||
sta _target+1 ; Modifies the immediate operand of the LDA at _target
|
||||
...
|
||||
_target lda #$00 ; This #$00 will be replaced by #$42 at runtime
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
## CPU Quirks and Pitfalls
|
||||
## Reference
|
||||
|
||||
### JMP ($xxFF) Page Wrap Bug (NMOS 6502)
|
||||
- **Problem**: On original 6502 CPUs, `jmp ($caff)` will fetch the LSB from `$caff` but the MSB from `$ca00` (instead of `$cb00`).
|
||||
- **Target**: Affects C64, C128, PET32.
|
||||
- **Solution**: Avoid placing indirect jump vectors on a page boundary, or use the CX16 (65C02) which fixed this bug.
|
||||
### Primary: 6502 Family CPU Reference
|
||||
|
||||
### BRK Instruction and the "Signature Byte"
|
||||
- **Behavior**: After a `BRK` instruction, the return address on the stack is incremented by **2**. This means the CPU skips the byte immediately following the `BRK` opcode.
|
||||
- **Usage**: This skipped byte is often used as a "signature" or parameter byte for the BRK handler.
|
||||
<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.
|
||||
|
||||
### BIT Instruction Flags
|
||||
- **Absolute/Zero-page**: `bit $1234` copies bit 7 of the memory value to the **N** flag and bit 6 to the **V** flag. The **Z** flag is set based on `A AND memory`.
|
||||
- **Immediate (65C02 only)**: `bit #$01` only affects the **Z** flag; it does **NOT** modify N or V.
|
||||
- **NMOS 6502**: Does **not** support `bit #imm`.
|
||||
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.
|
||||
|
||||
### The "B" (Break) Flag
|
||||
- **Quirk**: The B flag (bit 4 of the status register) doesn't actually exist in the hardware status register. It only exists on the stack after a `PHP` or `BRK` instruction (set to 1) or a hardware IRQ/NMI (set to 0).
|
||||
- **Detection**: To tell if an interrupt was caused by `BRK` or a hardware IRQ, your handler must `pla`, `and #$10`, and check the result.
|
||||
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.
|
||||
|
||||
### Decimal Mode Flag (D) Persistence
|
||||
- **Pitfall**: On NMOS 6502, the `D` flag is **not** cleared on interrupt. Always use `cld` in IRQ handlers. On 65C02, it is cleared automatically, but `cld` is still good practice.
|
||||
- **ADC/SBC**: Be extremely careful with arithmetic if you haven't explicitly set or cleared the `D` flag, as its state might be unknown.
|
||||
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.
|
||||
|
||||
## Optimization Tips
|
||||
- **`stz` (65C02 only)**: Saves cycles and bytes compared to `lda #0`, `sta ...`.
|
||||
- **`bra` (65C02 only)**: Shorter and usually faster than `jmp`.
|
||||
- **Avoid `clc` before `bcc`**: `cmp` already sets the carry flag correctly for `bcc`/`bcs`.
|
||||
- **`inx` / `dex` vs `clc`+`adc #1`**: Incrementing/decrementing is faster and doesn't affect the carry flag.
|
||||
- **Zero-page usage**: Accessing variables in zeropage is 1 cycle faster and 1 byte shorter than absolute addressing.
|
||||
- **Cycle Counting**: Most instructions take 2-4 cycles. `jsr` takes 6, `rts` takes 6. Branches take 2 (no branch), 3 (branch taken), or 4 (branch taken across page boundary).
|
||||
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.
|
||||
|
||||
## 64tass Macros
|
||||
Define macros for common tasks:
|
||||
```asm
|
||||
pushax .macro
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
.endm
|
||||
### Secondary: WDC 65C02 reference
|
||||
|
||||
popax .macro
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
.endm
|
||||
```
|
||||
Call them with `#pushax` and `#popax`.
|
||||
<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.
|
||||
|
||||
## IRQ Handler Best Practices
|
||||
- **Clear the Decimal Flag (`cld`)**: On the original 6502, the decimal flag (`D`) is **not** automatically cleared when an interrupt occurs. If the interrupted code was in decimal mode, your handler will also run in decimal mode, causing arithmetic errors. Always call `cld` at the beginning of your handler. The 65C02 clears it automatically, but `cld` is still recommended for portability.
|
||||
- Keep handlers extremely short and fast — they run with interrupts disabled and steal cycles from the main program.
|
||||
- Do NOT do lengthy processing, I/O, or complex subroutine calls inside the handler.
|
||||
- Instead, set a boolean flag or semaphore that the main loop checks periodically, and do the actual work there.
|
||||
### Neither reference covers the assembler
|
||||
|
||||
## Invoking the Assembler (64tass)
|
||||
If you need to manually invoke `64tass` to assemble a generated `.asm` file, you should be aware of the default arguments that `prog8c` supplies to ensure compatibility with the generated code:
|
||||
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'`.
|
||||
|
||||
- `--ascii`: **CRITICAL.** Prog8 generates character and string data in ASCII. Without this flag, `64tass` defaults to PETSCII, which will garble your strings.
|
||||
- `--case-sensitive`: Prog8 is case-sensitive and expects the assembler to be as well.
|
||||
- `--long-branch`: Enables automatic conversion of relative branches (`beq`, `bne`, etc.) to absolute jumps if the target is out of range. Prog8 relies on this.
|
||||
- `-Wno-implied-reg`: Suppresses warnings when the accumulator `a` is omitted from instructions like `rol`, `lsr`, etc. (though the skill recommends always using `rol a` for clarity).
|
||||
- `-Wall`: Enables all warnings.
|
||||
- `--cbm-prg` (or `--atari-xex` / `--nostart`): Sets the output format and adds the appropriate load address header.
|
||||
## Tools
|
||||
|
||||
### Optional but Recommended for Debugging:
|
||||
- `--vice-labels --labels=labels.txt`: Generates a label file that can be loaded into the VICE monitor (`load_labels "labels.txt"`) to see your Prog8 symbol names while debugging.
|
||||
- `--list=listing.txt`: Generates a full assembly listing file with addresses and opcodes.
|
||||
Prog8 invokes 64tass with `--ascii --case-sensitive --long-branch -Wall` plus
|
||||
the target output option such as `--cbm-prg`. For manual assembly:
|
||||
|
||||
### Example manual invocation:
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg -o myprogram.prg myprogram.asm
|
||||
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`.
|
||||
+140
-251
@@ -1,279 +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
|
||||
- **Git operations**: When moving, renaming, or deleting git-tracked files, **always use `git mv` or `git rm`** instead of plain `mv`/`rm`. This preserves history and properly stages the change. Plain `mv`/`rm` causes git to see them as delete+add (losing history).
|
||||
- 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), `-dumpsymbols` (print all symbols), `-dumpvars` (print all variables)
|
||||
- **Library search (preferred)**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded library files. Extremely useful to quickly find library routines, variables, strings, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines; `prog8c -libsearch "sin"` finds math functions)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection (less common, use `-libsearch` first)
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive to speed up multiple successive compilations — must be passed on every `prog8c` invocation)
|
||||
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
|
||||
- **Compiler unit test snippets**: These are **prog8 code snippets embedded in the Prog8 compiler's own Kotlin unit tests** (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`, etc.). They are NOT standalone Prog8 programs:
|
||||
- Keep them self-contained — avoid `%import` directives so they don't depend on library files (the test setup may not have the library search path configured)
|
||||
- Use the `cx16` target (not `virtual`, unless specifically testing the VM)
|
||||
- Consider setting `writeAssembly=false` if the test only needs to check the generated AST (much faster)
|
||||
- Use `optimize=false` by default for these snippets
|
||||
- Do NOT use the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern in these snippets — that's only for real CX16 emulator runs
|
||||
- **`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
|
||||
- To point a typed pointer at a `memory()` block, assign the uword directly: `^^MyStruct ptr = memory("name", size)`. The `^^Type:expression` syntax (below) only works with array literals, not general uword expressions.
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable). This `^^Type:[...]` syntax does NOT work with variables or `memory()` — only literal arrays.
|
||||
- Struct definitions must be inside a block, not at file level.
|
||||
- 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`, `@zp`, `@requirezp`, `@dirty`, `@nosplit` are **tags** that go on variable declarations. **Place them after the datatype (and array specifiers), before the variable name(s):**
|
||||
```
|
||||
private ubyte[8] @shared vera_storage ; single var with tag
|
||||
ubyte @zp @shared varname ; multiple tags allowed
|
||||
ubyte @requirezp var1 ; require zeropage
|
||||
ubyte[] @shared names ; array with tag
|
||||
```
|
||||
The grammar is: `[private] datatype [arraydims] [tags...] identifierlist`
|
||||
- `@shared` marks variables as "used by external code" (assembly), prevents the optimizer from removing them
|
||||
- `@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.
|
||||
- **WARNING: Virtual registers in ISR/IRQ handlers**: The virtual registers R0-R15 are *not preserved* across the IRQ handler call. If your handler uses them, it will corrupt the interrupted program's state. Either avoid using them in the handler, or save/restore with `cx16.save_virtual_registers()` / `cx16.restore_virtual_registers()`. This applies to all targets, not just CX16.
|
||||
- **WARNING: Long operations clobber R12-R15**: Some operations on `long` values use R12-R15 as temporary storage and will silently overwrite them. Do not rely on R12-R15 values when working with longs, and avoid using R12-R15 explicitly if your code uses long arithmetic.
|
||||
- **WARNING: VERA registers in ISR handlers (CX16)**: If your IRQ handler reads or writes VERA control registers (e.g., `cx16.VERA_DATA0`, `cx16.VERA_ADDR_L`, etc.), you must save and restore the VERA context around the handler's work using `cx16.save_vera_context()` / `cx16.restore_vera_context()`. Without this, the handler will corrupt any VERA operations (tilemap updates, sprite positioning, etc.) happening in the interrupted main program.
|
||||
- **IRQ handler best practices**: Keep handlers extremely short and fast — they run with interrupts disabled and steal cycles from the main program. Do NOT do lengthy processing, I/O, or complex subroutine calls inside the handler. Instead, set a boolean flag or semaphore that the main loop checks periodically, and do the actual work there.
|
||||
- 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`
|
||||
- Assigning a uword address to a `str` field of a struct is done by direct assignment. Compute the address in a uword variable first, then assign: `cx16.r0 = &namebufs + offset; entry.name = cx16.r0`. The `^^ubyte:(expr)` cast syntax does not parse — use a temp uword instead.
|
||||
- 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)
|
||||
- If-expressions for simple value assignments based on a choice
|
||||
- **Optional braces in if/else**: when the `if` or `else` body is a single statement, the `{ }` can be omitted. Place the statement on the next line, indented. Example:
|
||||
```
|
||||
if x < 5
|
||||
txt.print("small")
|
||||
```
|
||||
- `defer` defers statement execution until scope exit. Multiple defers fire in **reverse registration order** (LIFO / stack order — last deferred runs first). A defer is only registered if execution reaches that statement — conditional paths that skip the `defer` line will not register it.
|
||||
- `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()`.
|
||||
|
||||
### The when Statement
|
||||
The `when` statement is a control flow construct that enables you to execute a specific action based on the value of an expression. It is generally more readable and often more efficient than a sequence of `if-else if` statements, as the compiler can optimize it into more efficient branching structures, such as a jump table.
|
||||
- **Expression**: Evaluates an expression and compares it against case values.
|
||||
- **Cases**: Defined by a value followed by the `->` operator.
|
||||
- **Blocks**: Use `{ }` to enclose multiple statements for a case.
|
||||
- **Else Clause**: Serves as a default handler; mandatory unless the expression type is fully covered.
|
||||
- **Efficiency**: Recommended for handling sets of fixed choices as it typically results in better assembly code.
|
||||
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.
|
||||
|
||||
## Loop Constructs
|
||||
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
|
||||
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.
|
||||
|
||||
### `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
|
||||
}
|
||||
## Types and Memory
|
||||
|
||||
; descending
|
||||
for i in 155 downto 20 {}
|
||||
for i in 155 to 20 step -1 {}
|
||||
- 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:
|
||||
|
||||
; iterate over array elements
|
||||
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
|
||||
uword num
|
||||
for num in fib {
|
||||
...
|
||||
}
|
||||
```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
|
||||
- **Don't use `private` on subroutines and variables** (including nested ones) unless the user asks for it. Everything is public by default in Prog8 — follow that convention.
|
||||
- `inline` keyword for subroutines to suggest inlining
|
||||
- No **function overloading** (except builtins) and no **polymorphism** in general. This means you must call specific routines for different types (e.g., `txt.print_ub(val)` vs `txt.print_w(val)` instead of a generic `print(val)`). 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 low-level assembly that gets arguments via registers and returns values in registers.
|
||||
- **Syntax**: `[private] [inline] asmsub subname(params) [clobbers(regs)] [-> returns] { %asm {{ ... }} }`
|
||||
- **Body Restriction**: The body of an `asmsub` **must only contain** a single `%asm {{ ... }}` node. Regular Prog8 statements or nested blocks are NOT allowed.
|
||||
- **Parameters**: `type name @register` (e.g., `ubyte val @A`, `uword addr @AX`, `float f @FAC1`).
|
||||
- **Return Values**: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use).
|
||||
- **Registers**:
|
||||
- **8-bit**: `@A`, `@X`, `@Y`
|
||||
- **16-bit**: `@AX`, `@AY`, `@XY` (register pairs)
|
||||
- **Float**: `@FAC1`, `@FAC2` (Floating Point Accumulators)
|
||||
- **Virtual**: `@R0`–`@R15` (16-bit), `@R0R1`–`@R14R15` (32-bit combined)
|
||||
- **Status Flags (for returns)**: `@Pc` (Carry), `@Pz` (Zero), `@Pv` (Overflow), `@Pn` (Negative)
|
||||
- **Clobbers**: `clobbers (A, X, Y)` — list all hardware registers modified by the routine.
|
||||
- **Parameter names** are for documentation and type checking only. Use the registers in your assembly code.
|
||||
- **Inlining**: `inline asmsub` will paste the assembly code directly at the call site, avoiding `jsr`/`rts` overhead.
|
||||
- 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.
|
||||
|
||||
## External Subroutines (extsub)
|
||||
Used to call routines at fixed memory addresses (like ROM KERNAL routines or third-party drivers).
|
||||
- **Syntax**: `[private] extsub [@bank <value>] address = subname(params) [clobbers(regs)] [-> returns]`
|
||||
- **Address**: Can be a hex literal (`$C000`) or a constant expression.
|
||||
- **Bank (optional)**: `@bank <integer>` (constant bank) or `@bank <identifier>` (variable bank).
|
||||
- **No Body**: Unlike `asmsub`, `extsub` has no `{ }` body; it just maps a signature to an address.
|
||||
- **Example**:
|
||||
```prog8
|
||||
; CX16 KERNAL CHROUT
|
||||
extsub $FFD2 = chrout(ubyte char @A) clobbers(A, X, Y)
|
||||
## Interrupts and Hardware
|
||||
|
||||
; Routine in a specific RAM bank
|
||||
extsub @bank 10 $C09F = audio_init() clobbers(A, X, Y) -> bool @Pc
|
||||
```
|
||||
- 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.
|
||||
|
||||
## Assembly Programming Details
|
||||
- **Symbol prefixes**: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- **Fully qualified names**: `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**: use 65C02 instructions (STZ, PHX, etc). Others: 6502 only
|
||||
- **Assembly syntax**: 64tass. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- **Instructions** like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`
|
||||
- **Anonymous labels**: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
- **Register Aliases**: It's common to define aliases at the start of an `asmsub`:
|
||||
```prog8
|
||||
asmsub my_routine(uword ptr @AX) {
|
||||
%asm {{
|
||||
ptr_lo = p8zp_scratch_w1
|
||||
ptr_hi = p8zp_scratch_w1+1
|
||||
sta ptr_lo
|
||||
stx ptr_hi
|
||||
}}
|
||||
}
|
||||
```
|
||||
## Verification
|
||||
|
||||
## 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()`). **Note: Prog8 has no function overloading**, so you cannot use `txt.print(number)` — you must call the specific routine for the type (e.g., `txt.print_ub(val)` for an unsigned byte).
|
||||
- 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)`
|
||||
|
||||
## 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 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. They are syntactic sugar for a list of `const` declarations, not a type. 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)
|
||||
- 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/
|
||||
@@ -27,11 +27,17 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build and test with Gradle
|
||||
- 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 --console=plain
|
||||
./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@v6
|
||||
with:
|
||||
@@ -40,3 +46,9 @@ jobs:
|
||||
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/
|
||||
+7
-1
@@ -15,8 +15,11 @@ out-old/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
*.elf
|
||||
*.o
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
@@ -42,4 +45,7 @@ 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.21" />
|
||||
<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.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-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.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.1/ksim65-v2.1.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/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 />
|
||||
<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>
|
||||
@@ -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
|
||||
|
||||
@@ -4,18 +4,19 @@ 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 a huge amoung 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)
|
||||
|
||||
@@ -94,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
|
||||
|
||||
@@ -104,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
|
||||
}
|
||||
|
||||
@@ -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.21"
|
||||
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.1")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-37
@@ -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,8 +122,9 @@ 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)
|
||||
@@ -159,16 +151,16 @@ internal class AssemblyProgram(
|
||||
|
||||
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")
|
||||
@@ -176,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])
|
||||
@@ -189,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,51 +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)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR)),
|
||||
"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,6 +75,15 @@ 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
|
||||
@@ -76,12 +92,10 @@ val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.
|
||||
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 || this == BaseDataType.ARRAY_POINTER
|
||||
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
|
||||
|
||||
@@ -112,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" }
|
||||
@@ -139,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
|
||||
@@ -155,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -217,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")
|
||||
}
|
||||
}
|
||||
@@ -273,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")
|
||||
}
|
||||
}
|
||||
@@ -286,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"
|
||||
}
|
||||
@@ -320,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["
|
||||
@@ -337,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")
|
||||
}
|
||||
}
|
||||
@@ -359,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
|
||||
@@ -373,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
|
||||
@@ -420,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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -20,6 +20,25 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
translateFunctioncall(fcall, null, discardResult = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Several builtins can only produce their result in a CPU register, and which one that is
|
||||
* depends on the width of the result: the byte-returning ones (lsb/msb) take a single register,
|
||||
* the word-returning ones (lsw/msw) and popw take a register pair, and the builtin picks the
|
||||
* specific register. [hardwareReg] is the register to fall back on when the caller has no
|
||||
* preference, or when it asked for something the builtin cannot deliver to directly.
|
||||
*
|
||||
* The caller's requested register is honoured when it is a real CPU register. A CX16 virtual
|
||||
* register (for example an asmsub parameter declared as @R0) is not: those are memory locations,
|
||||
* so the builtin produces its result in [hardwareReg] and the caller's assignment then stores it
|
||||
* into the virtual register. Passing the virtual register through would make the builtin reject
|
||||
* it as an invalid result register.
|
||||
*
|
||||
* (funcPeekW and funcMkword already handle virtual registers themselves.)
|
||||
*/
|
||||
private fun withResultRegister(requested: RegisterOrPair?, hardwareReg: RegisterOrPair, compute: (RegisterOrPair) -> Array<RegisterOrPair>): Array<RegisterOrPair> =
|
||||
if (requested != null && requested !in Cx16VirtualRegisters) compute(requested)
|
||||
else compute(hardwareReg)
|
||||
|
||||
private fun translateFunctioncall(fcall: PtFunctionCall, firstReturnRegister: RegisterOrPair?, discardResult: Boolean): Array<RegisterOrPair> {
|
||||
|
||||
// Note: returns the actual register(s) that the return value is in, so the caller should still make sure to place it into the actual correct destination.
|
||||
@@ -31,12 +50,12 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val sscope = fcall.definingISub()
|
||||
|
||||
return when (fcall.name) {
|
||||
"msw" -> funcMsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"lsw" -> funcLsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"msb" -> funcMsb(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb__long" -> funcMsbLong(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb" -> funcLsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb__long" -> funcLsb(fcall,true, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msw" -> withResultRegister(firstReturnRegister, RegisterOrPair.AY) { funcMsw(fcall, it) }
|
||||
"lsw" -> withResultRegister(firstReturnRegister, RegisterOrPair.AY) { funcLsw(fcall, it) }
|
||||
"msb" -> withResultRegister(firstReturnRegister, RegisterOrPair.A) { funcMsb(fcall, false, it) }
|
||||
"msb__long" -> withResultRegister(firstReturnRegister, RegisterOrPair.A) { funcMsb(fcall, true, it) }
|
||||
"lsb" -> withResultRegister(firstReturnRegister, RegisterOrPair.A) { funcLsb(fcall, false, it) }
|
||||
"lsb__long" -> withResultRegister(firstReturnRegister, RegisterOrPair.A) { funcLsb(fcall, true, it) }
|
||||
"mkword" -> funcMkword(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"mklong", "mklong2" -> funcMklong(fcall, firstReturnRegister ?: RegisterOrPair.R14R15)
|
||||
"clamp__byte", "clamp__ubyte" -> funcClamp(fcall)
|
||||
@@ -98,8 +117,8 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
"pushw" -> funcPushW(fcall)
|
||||
"pushl" -> funcPushL(fcall)
|
||||
"pushf" -> funcPushF(fcall)
|
||||
"pop" -> funcPop(firstReturnRegister ?: RegisterOrPair.A)
|
||||
"popw" -> funcPopW(firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"pop" -> withResultRegister(firstReturnRegister, RegisterOrPair.A) { funcPop(it) }
|
||||
"popw" -> withResultRegister(firstReturnRegister, RegisterOrPair.AY) { funcPopW(it) }
|
||||
"popl" -> funcPopL()
|
||||
"popf" -> funcPopF()
|
||||
else -> throw AssemblyError("sizeof must have been replaced with a constant ${fcall.position}")
|
||||
@@ -247,11 +266,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
// prog8_math.square does its own range check and falls back to the general
|
||||
// multiply_words for values outside -255..255, so the fast path stays valid.
|
||||
asmgen.out(" jsr prog8_math.square") // result is in AY
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true)
|
||||
// prog8_math.square_long does its own range check and falls back to the general
|
||||
// multiply_longs when the long doesn't fit in a signed 16-bit word.
|
||||
asmgen.out(" jsr prog8_math.square_long") // result is in R14:R15
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
@@ -376,6 +399,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
val identifier = fcall.args[0] as? PtIdentifier
|
||||
if(identifier!=null) {
|
||||
// NOTE: `jmp (ptr)` below has the 6502 page-wrap bug on plain
|
||||
// 6502 if the pointer (the identifier, a variable holding a
|
||||
// routine address) lands at $xxFF. 65C02 is fine. Latent; only
|
||||
// misbehaves for variables placed at a $xxFF address. The
|
||||
// new6502 codegen's CALLI has the same hazard.
|
||||
asmgen.out("""
|
||||
; push a return address so the jmp becomes indirect jsr
|
||||
lda #>((+)-1)
|
||||
@@ -387,6 +415,9 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
// NOTE: same 6502 page-wrap pitfall as above: the pointer is
|
||||
// staged through P8ZP_SCRATCH_W2 before the indirect jmp, so the
|
||||
// scratch word must not land at $xxFF on plain 6502. 65C02 is fine.
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
|
||||
asmgen.out("""
|
||||
; push a return address so the jmp becomes indirect jsr
|
||||
@@ -401,15 +432,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
if(targetName !in arrayOf("cx16", "c64", "c128"))
|
||||
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
|
||||
|
||||
val jsrfar = when(targetName) {
|
||||
val jsrfar = when(val targetName = asmgen.options.compTarget.name) {
|
||||
"cx16" -> "cx16.JSRFAR"
|
||||
"c64" -> "c64.x16jsrfar"
|
||||
"c128" -> "c128.x16jsrfar"
|
||||
else -> TODO("jsrfar routine")
|
||||
else -> "$targetName.x16jsrfar"
|
||||
}
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
@@ -421,7 +448,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
if(asmgen.options.romable)
|
||||
TODO("non-const callfar (jsrfar) yet that's usable in ROM ${fcall.position}")
|
||||
asmgen.errors.err("non-const callfar (jsrfar) has no ROMable code generation", fcall.position)
|
||||
// self-modifying code: set jsrfar arguments
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
@@ -478,7 +505,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
if(asmgen.options.romable)
|
||||
TODO("non-const callfar2 (jsrfar) yet that's usable in ROM ${fcall.position}")
|
||||
asmgen.errors.err("non-const callfar2 (jsrfar) has no ROMable code generation", fcall.position)
|
||||
// self-modifying code: set jsrfar arguments
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
@@ -763,7 +790,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 byte ptr indexing ${what.position}")
|
||||
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
@@ -789,7 +816,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 word ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
@@ -841,7 +868,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" ror ${varname},x")
|
||||
}
|
||||
@@ -883,7 +910,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
|
||||
@@ -950,7 +977,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 byte ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
|
||||
@@ -976,7 +1003,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
is PtArrayIndexer -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
|
||||
@@ -1024,7 +1051,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" rol ${varname},x")
|
||||
}
|
||||
@@ -1066,7 +1093,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
|
||||
@@ -1140,7 +1167,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val msbAdd: Int
|
||||
if(indexer.splitWords) {
|
||||
val arrayVariable = indexer.variable ?: TODO("ptr indexing ${indexer.position}")
|
||||
indexer.setChild(0, PtIdentifier(arrayVariable.name + (if(msb) "_msb" else "_lsb"), DataType.arrayFor(BaseDataType.UBYTE, false), arrayVariable.position))
|
||||
indexer.setChild(0, PtIdentifier(arrayVariable.name + (if(msb) "_msb" else "_lsb"), DataType.arrayFor(BaseDataType.UBYTE, asmgen.options.compTarget), arrayVariable.position))
|
||||
elementSize = 1
|
||||
msbAdd = 0
|
||||
} else {
|
||||
@@ -1197,7 +1224,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val arg = fcall.args.single()
|
||||
if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
when(val dt = arg.type.base) {
|
||||
when(arg.type.base) {
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
@@ -1435,7 +1462,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return emptyArray()
|
||||
} else if(addressOfIdentifier!=null && (addressOfIdentifier.type.isWord || addressOfIdentifier.type.isPointer || addressOfIdentifier.type.isByteArray)) {
|
||||
var varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(addressOfIdentifier.type.isSplitWordArray) {
|
||||
if(addressOfIdentifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
val addrOf = result.first as PtAddressOf
|
||||
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
|
||||
}
|
||||
@@ -1817,7 +1844,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
} else if(addressOfIdentifier!=null && (addressOfIdentifier.type.isWord || addressOfIdentifier.type.isPointer || addressOfIdentifier.type.isByteArray)) {
|
||||
var varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(addressOfIdentifier.type.isSplitWordArray) {
|
||||
if(addressOfIdentifier.type.isSplitWordArray(asmgen.options.compTarget)) {
|
||||
val addrOf = result.first as PtAddressOf
|
||||
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
|
||||
}
|
||||
@@ -1976,27 +2003,314 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
|
||||
private fun inlineClampByte(value: PtExpression, minVal: Int, maxVal: Int, signed: Boolean) {
|
||||
val typeMin = if (signed) -128 else 0
|
||||
val typeMax = if (signed) 127 else 255
|
||||
assignAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
||||
when {
|
||||
minVal == typeMin && maxVal == typeMax -> {
|
||||
// no-op: the value in A is already the clamped result
|
||||
}
|
||||
minVal == typeMin -> {
|
||||
// upper clamp only: min(x, maxVal)
|
||||
emitByteUpperClamp(maxVal, signed)
|
||||
}
|
||||
maxVal == typeMax -> {
|
||||
// lower clamp only: max(x, minVal)
|
||||
emitByteLowerClamp(minVal, signed)
|
||||
}
|
||||
else -> {
|
||||
// two-sided clamp: upper then lower, sequentially on the running value in A
|
||||
emitByteUpperClamp(maxVal, signed)
|
||||
emitByteLowerClamp(minVal, signed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitByteUpperClamp(maxVal: Int, signed: Boolean) {
|
||||
// result = min(x, maxVal); x is in A on entry
|
||||
// mirrors the proven func_clamp_byte sequence (sec/sbc + bmi means "x < max")
|
||||
val imm = "#${(maxVal and 0xff).toHex()}"
|
||||
if (signed) {
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val done = asmgen.makeLabel("clampdone")
|
||||
asmgen.out(listOf(
|
||||
" tay",
|
||||
" sec",
|
||||
" sbc $imm",
|
||||
" bvc +",
|
||||
" eor #128",
|
||||
"+ bmi $keep",
|
||||
" lda $imm",
|
||||
" jmp $done",
|
||||
"$keep tya",
|
||||
done
|
||||
).joinToString("\n"))
|
||||
} else {
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
asmgen.out(listOf(
|
||||
" cmp $imm",
|
||||
" bcc $keep",
|
||||
" lda $imm",
|
||||
keep
|
||||
).joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitByteLowerClamp(minVal: Int, signed: Boolean) {
|
||||
// result = max(x, minVal); x is in A on entry
|
||||
// mirrors the proven func_clamp_byte sequence (sec/sbc + bpl means "x >= min")
|
||||
val imm = "#${(minVal and 0xff).toHex()}"
|
||||
if (signed) {
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val done = asmgen.makeLabel("clampdone")
|
||||
asmgen.out(listOf(
|
||||
" tay",
|
||||
" sec",
|
||||
" sbc $imm",
|
||||
" bvc +",
|
||||
" eor #128",
|
||||
"+ bpl $keep",
|
||||
" lda $imm",
|
||||
" jmp $done",
|
||||
"$keep tya",
|
||||
done
|
||||
).joinToString("\n"))
|
||||
} else {
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
asmgen.out(listOf(
|
||||
" cmp $imm",
|
||||
" bcs $keep",
|
||||
" lda $imm",
|
||||
keep
|
||||
).joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcClampWordConst(minExpr: PtExpression, maxExpr: PtExpression, minOp: Pair<String, String>, maxOp: Pair<String, String>, signed: Boolean) {
|
||||
// value is already in AY on entry, result in AY
|
||||
val minVal = (asmgen.unwrapCasts(minExpr) as? PtNumber)?.number?.toInt()
|
||||
val maxVal = (asmgen.unwrapCasts(maxExpr) as? PtNumber)?.number?.toInt()
|
||||
val typeMin = if (signed) -32768 else 0
|
||||
val typeMax = if (signed) 32767 else 65535
|
||||
val oneSided = minVal != null && minVal == typeMin || maxVal != null && maxVal == typeMax
|
||||
val fullRange = minVal != null && maxVal != null && minVal == typeMin && maxVal == typeMax
|
||||
when {
|
||||
fullRange -> {
|
||||
// no-op: the value in AY is already the clamped result
|
||||
}
|
||||
signed && minVal == typeMin -> {
|
||||
// upper clamp only
|
||||
emitSignedWordClampProlog()
|
||||
emitSignedWordUpperCompare(maxOp)
|
||||
emitWordLoadValueFromW1()
|
||||
}
|
||||
signed && maxVal == typeMax -> {
|
||||
// lower clamp only
|
||||
emitSignedWordClampProlog()
|
||||
emitSignedWordLowerCompare(minOp)
|
||||
}
|
||||
signed -> {
|
||||
emitSignedWordClampProlog()
|
||||
emitSignedWordUpperCompare(maxOp)
|
||||
emitSignedWordLowerCompare(minOp)
|
||||
}
|
||||
!oneSided -> {
|
||||
// uword, two-sided clamp
|
||||
emitWordUpperClamp(maxOp)
|
||||
emitWordLowerClamp(minOp)
|
||||
}
|
||||
minVal == typeMin -> {
|
||||
// uword, upper clamp only
|
||||
emitWordUpperClamp(maxOp)
|
||||
}
|
||||
else -> {
|
||||
// uword, lower clamp only
|
||||
emitWordLowerClamp(minOp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitSignedWordClampProlog() {
|
||||
asmgen.out(listOf(
|
||||
" sta P8ZP_SCRATCH_W1",
|
||||
" sty P8ZP_SCRATCH_W1+1"
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
private fun emitWordLoadValueFromW1() {
|
||||
asmgen.out(listOf(
|
||||
" lda P8ZP_SCRATCH_W1",
|
||||
" ldy P8ZP_SCRATCH_W1+1"
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
// signed compare of max/min (16-bit immediates) against the running value in P8ZP_SCRATCH_W1.
|
||||
// The result of the two-sided compare is the high byte of (bound - value), with the
|
||||
// overflow-compensated sign bit set, mirroring the func_clamp_word sequence.
|
||||
|
||||
private fun emitSignedWordUpperCompare(maxOp: Pair<String, String>) {
|
||||
// if value > max: running value becomes max
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val done = asmgen.makeLabel("clampdone")
|
||||
asmgen.out(listOf(
|
||||
" ldy ${maxOp.second}",
|
||||
" lda ${maxOp.first}",
|
||||
" cmp P8ZP_SCRATCH_W1",
|
||||
" tya",
|
||||
" sbc P8ZP_SCRATCH_W1+1",
|
||||
" bvc $keep",
|
||||
" eor #$80",
|
||||
keep,
|
||||
" bpl $done",
|
||||
" lda ${maxOp.first}",
|
||||
" ldy ${maxOp.second}",
|
||||
" sta P8ZP_SCRATCH_W1",
|
||||
" sty P8ZP_SCRATCH_W1+1",
|
||||
done
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
private fun emitSignedWordLowerCompare(minOp: Pair<String, String>) {
|
||||
// if value < min: result becomes min, otherwise the running value (in W1)
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val set = asmgen.makeLabel("clampset")
|
||||
val done = asmgen.makeLabel("clampdone")
|
||||
asmgen.out(listOf(
|
||||
" ldy ${minOp.second}",
|
||||
" lda ${minOp.first}",
|
||||
" cmp P8ZP_SCRATCH_W1",
|
||||
" tya",
|
||||
" sbc P8ZP_SCRATCH_W1+1",
|
||||
" bvc $keep",
|
||||
" eor #$80",
|
||||
keep,
|
||||
" bpl $set",
|
||||
" lda P8ZP_SCRATCH_W1",
|
||||
" ldy P8ZP_SCRATCH_W1+1",
|
||||
" jmp $done",
|
||||
set,
|
||||
" lda ${minOp.first}",
|
||||
" ldy ${minOp.second}",
|
||||
done
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
private fun emitWordUpperClamp(maxOp: Pair<String, String>) {
|
||||
// uword: clamp the value in AY down to maxOp (result = min(value, max))
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val set = asmgen.makeLabel("clampset")
|
||||
asmgen.out(listOf(
|
||||
" cpy ${maxOp.second}",
|
||||
" bcc $keep",
|
||||
" bne $set",
|
||||
" cmp ${maxOp.first}",
|
||||
" bcc $keep",
|
||||
" beq $keep",
|
||||
set,
|
||||
" lda ${maxOp.first}",
|
||||
" ldy ${maxOp.second}",
|
||||
keep
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
private fun emitWordLowerClamp(minOp: Pair<String, String>) {
|
||||
// uword: clamp the value in AY up to minOp (result = max(value, min))
|
||||
val keep = asmgen.makeLabel("clampkeep")
|
||||
val set = asmgen.makeLabel("clampset")
|
||||
val done = asmgen.makeLabel("clampdone")
|
||||
asmgen.out(listOf(
|
||||
" cpy ${minOp.second}",
|
||||
" bcc $set",
|
||||
" bne $keep",
|
||||
" cmp ${minOp.first}",
|
||||
" bcc $set",
|
||||
" beq $keep",
|
||||
keep,
|
||||
" jmp $done",
|
||||
set,
|
||||
" lda ${minOp.first}",
|
||||
" ldy ${minOp.second}",
|
||||
done
|
||||
).joinToString("\n"))
|
||||
}
|
||||
|
||||
private fun funcClamp(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
if (!signed && fcall.args[1] is PtNumber && fcall.args[2] is PtNumber) {
|
||||
// unsigned (ubyte) clamp with constant bounds: emit a small inline clamp.
|
||||
// Only worthwhile for ubyte: the signed inline sequence is larger than the
|
||||
// shared jsr func_clamp_byte subroutine, so signed clamps keep using that.
|
||||
val minVal = (fcall.args[1] as PtNumber).number.toInt()
|
||||
val maxVal = (fcall.args[2] as PtNumber).number.toInt()
|
||||
inlineClampByte(fcall.args[0], minVal, maxVal, signed)
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
// simple values (number/variable) do not clobber W1, so min/max can be evaluated first
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
} else {
|
||||
// any non-simple argument may use W1 as a temporary, so evaluate value and minimum
|
||||
// first and preserve them while the maximum is evaluated
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value first (may use W1 as temp)
|
||||
asmgen.out(" pha")
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha")
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1")
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in A
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
val minOp = asmgen.getStaticAddressLowHigh(fcall.args[1])
|
||||
val maxOp = asmgen.getStaticAddressLowHigh(fcall.args[2])
|
||||
val minIsConst = asmgen.unwrapCasts(fcall.args[1]) !is PtIdentifier
|
||||
val maxIsConst = asmgen.unwrapCasts(fcall.args[2]) !is PtIdentifier
|
||||
if (minOp != null && maxOp != null && minIsConst && maxIsConst) {
|
||||
// both bounds are compile-time constants (numeric literals or fixed label addresses):
|
||||
// emit a small inline clamp instead of evaluating bounds into scratch + jsr func_clamp_word/uword
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
funcClampWordConst(fcall.args[1], fcall.args[2], minOp, maxOp, signed)
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
} else {
|
||||
// any non-simple argument may use P8ZP_SCRATCH_W1/W2 as temporaries (e.g. via multiply_words/divmod_w_asm),
|
||||
// so evaluate value and minimum first and preserve them while the maximum is evaluated
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
asmgen.out(" pha\n tya\n pha") // save AY on stack (A low, Y high)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha") // save minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1") // restore minimum
|
||||
asmgen.out(" pla\n tay\n pla") // restore AY
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in AY
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
|
||||
} else {
|
||||
// same ordering issue: value may clobber R10R11/R12R13 via multiply_longs/div_longs
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val first
|
||||
asmgen.out(" lda cx16.r14\n pha\n lda cx16.r14+1\n pha\n lda cx16.r15\n pha\n lda cx16.r15+1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.out(" pla\n sta cx16.r15+1\n pla\n sta cx16.r15\n pla\n sta cx16.r14+1\n pla\n sta cx16.r14")
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_long")
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
@@ -2006,11 +2320,20 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcMin(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
if (signed) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
} else {
|
||||
// left or right may use B1/REG as temp (e.g. multiply), so save left
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sec
|
||||
@@ -2023,8 +2346,18 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
} else {
|
||||
// right in B1, left in A - left may use B1 as temp, so save right
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
// now A holds left, need to compare again? Actually left already in A, right in B1 restored, so cmp is still valid
|
||||
// Re-do cmp after restore is not needed, the following cmp uses P8ZP_SCRATCH_B1 correctly
|
||||
}
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
@@ -2034,8 +2367,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
|
||||
}
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -2109,11 +2449,19 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcMax(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
if (signed) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sec
|
||||
@@ -2126,8 +2474,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" bcs +")
|
||||
asmgen.out("""
|
||||
@@ -2137,8 +2492,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
|
||||
}
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -2212,9 +2574,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
internal fun funcMklong(fcall: PtFunctionCall, resultReg: RegisterOrPair, targetVarName: String? = null): Array<RegisterOrPair> {
|
||||
fun isArgRegister(expression: PtExpression, reg: RegisterOrPair): Boolean {
|
||||
if(expression !is PtIdentifier)
|
||||
return false
|
||||
return expression.name.startsWith("cx16.${reg.name.lowercase()}")
|
||||
return expression is PtIdentifier && expression.name.startsWith("cx16.${reg.name.lowercase()}")
|
||||
}
|
||||
|
||||
val (r1, r2) = when(resultReg) {
|
||||
@@ -2346,55 +2706,36 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
private fun funcMsbLong(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
private fun funcMsb(fcall: PtFunctionCall, fromLong: Boolean, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else if(arg is PtArrayIndexer) {
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
if (!arg.type.isWord && !arg.type.isPointer)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
}
|
||||
|
||||
return arrayOf(resultReg)
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isWord)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
val offset = if(fromLong) 3 else 1
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+$offset")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+$offset | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+$offset | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+$offset | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb${if(fromLong) " long" else ""}: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
if(arg is PtArrayIndexer) {
|
||||
if(fromLong)
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
|
||||
// just read the msb byte out of the word array
|
||||
if(arg.splitWords) {
|
||||
if(arg.variable==null)
|
||||
@@ -2416,15 +2757,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldx #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldy #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" ldy $arrayVar,x | ldx #0")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
@@ -2460,6 +2801,14 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
}
|
||||
} else if(fromLong) {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+$offset")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
@@ -2474,7 +2823,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong) throw AssemblyError("lsb__long requires long")
|
||||
} else {
|
||||
if (!arg.type.isWord) throw AssemblyError("lsb requires word")
|
||||
if (!arg.type.isWord && !arg.type.isPointer) throw AssemblyError("lsb requires word")
|
||||
}
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
@@ -2600,7 +2949,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun translateArguments(call: PtFunctionCall, funcname: String?, scope: IPtSubroutine?) {
|
||||
val signature = BuiltinFunctions.getValue(call.name)
|
||||
val callConv = signature.callConvention(call.args.map {
|
||||
val callConv = signature.callConventionFor6502(call.args.map {
|
||||
require(it.type.isNumericOrBool)
|
||||
it.type.base
|
||||
})
|
||||
@@ -2653,7 +3002,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
conv.dt==BaseDataType.LONG -> getSourceForLong(value)
|
||||
conv.dt.isPassByRef -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForUntypedAddressOf(false, program.memsizer), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = call
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
@@ -2672,7 +3021,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
conv.dt==BaseDataType.LONG -> AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
conv.dt.isPassByRef -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
|
||||
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForUntypedAddressOf(false, program.memsizer), false,value.position)
|
||||
addr.add(value)
|
||||
addr.parent = call
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
|
||||
@@ -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,15 +34,21 @@ 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)
|
||||
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")
|
||||
}
|
||||
@@ -49,6 +56,369 @@ internal class ForLoopsAsmGen(
|
||||
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")
|
||||
@@ -157,12 +527,7 @@ internal class ForLoopsAsmGen(
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
// TODO: implement 32-bit loop
|
||||
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
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) {
|
||||
@@ -822,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")
|
||||
@@ -920,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 -> {
|
||||
@@ -1091,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()
|
||||
}
|
||||
@@ -1121,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()
|
||||
}
|
||||
@@ -1150,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
|
||||
@@ -1163,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()
|
||||
}
|
||||
@@ -1194,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
|
||||
@@ -1216,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
|
||||
@@ -1241,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")
|
||||
@@ -1831,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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -723,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")
|
||||
@@ -774,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")
|
||||
@@ -802,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("""
|
||||
@@ -853,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("""
|
||||
@@ -1092,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
|
||||
}
|
||||
}
|
||||
@@ -1124,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")
|
||||
@@ -1150,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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
asmgen.out("; $cpu assembly code for '${program.name}'")
|
||||
asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
|
||||
asmgen.out("; prog8 compiler version: ${options.compilerVersion}")
|
||||
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
||||
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||
asmgen.out("")
|
||||
@@ -173,6 +174,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 +225,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 +249,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 +344,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 +360,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 +431,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 +640,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 +")
|
||||
}
|
||||
@@ -648,7 +679,6 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private class ZpStringWithInitial(
|
||||
val name: String,
|
||||
val alloc: MemoryAllocator.VarAllocation,
|
||||
val value: Pair<String, Encoding>
|
||||
)
|
||||
|
||||
@@ -665,7 +695,7 @@ internal class ProgramAndVarsGen(
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||
if(svar?.initializationStringValue!=null)
|
||||
result.add(ZpStringWithInitial(scopedName, variable.value, svar.initializationStringValue!!))
|
||||
result.add(ZpStringWithInitial(scopedName, svar.initializationStringValue!!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
+203
-7
@@ -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!!
|
||||
@@ -554,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)
|
||||
@@ -931,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""")
|
||||
@@ -1670,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)
|
||||
@@ -2060,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")
|
||||
}
|
||||
@@ -2371,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
|
||||
@@ -2386,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
|
||||
|
||||
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
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,12 +8,28 @@ class IRUnusedCodeRemover(
|
||||
private val irprog: IRProgram,
|
||||
private val errors: IErrorReporter
|
||||
) {
|
||||
/**
|
||||
* The symbols that struct instance initializers take the address of. These are plain data
|
||||
* references: nothing ever calls them, so without this the code they point at is considered
|
||||
* unused and removed, leaving a dangling reference in the instance initializer.
|
||||
* The same reasoning applies to the array initializers of variables, which are handled
|
||||
* separately at each of the use sites below.
|
||||
*/
|
||||
private fun structInstanceInitRefs(): Sequence<IRStSymbolicReference.Symbol> =
|
||||
irprog.st.allStructInstances()
|
||||
.flatMap { instance -> instance.values.asSequence().map { it.value } }
|
||||
.filterIsInstance<IRStSymbolicReference.Symbol>()
|
||||
|
||||
fun optimize(): Int {
|
||||
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
|
||||
|
||||
// remove empty blocks
|
||||
// remove empty blocks (but keep blocks with %option force_output,
|
||||
// and also blocks that contain labels -- labels are addressable symbols,
|
||||
// and blocks that still have variables in the symbol table -- a block with variables is not truly empty)
|
||||
irprog.blocks.reversed().forEach { block ->
|
||||
if(block.isEmpty()) {
|
||||
val hasLabels = block.children.any { it.label != null }
|
||||
val hasVariables = irprog.st.allVariables().any { it.name.startsWith(block.label + ".") }
|
||||
if(!hasLabels && !hasVariables && block.isEmpty() && !block.options.forceOutput) {
|
||||
irprog.blocks.remove(block)
|
||||
pruneSymboltable(block.label)
|
||||
numRemoved++
|
||||
@@ -33,7 +49,7 @@ class IRUnusedCodeRemover(
|
||||
blockVars.forEach { stVar ->
|
||||
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
|
||||
chunk.instructions.forEach { ins ->
|
||||
if(ins.labelSymbol == stVar.name) {
|
||||
if(ins.referencedSymbol() == stVar.name) {
|
||||
return // symbol occurs in an instruction
|
||||
}
|
||||
}
|
||||
@@ -48,6 +64,14 @@ class IRUnusedCodeRemover(
|
||||
return // symbol occurs in an initializer value (address-of this symbol)_
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.allStructInstances().forEach { instance ->
|
||||
if(instance.values.any { fieldValue ->
|
||||
val ref = fieldValue.value
|
||||
ref is IRStSymbolicReference.Symbol && ref.name.startsWith(blockLabel)
|
||||
})
|
||||
return // symbol occurs in a struct instance initializer value
|
||||
}
|
||||
}
|
||||
|
||||
val blockStructs = irprog.st.allStructDefs().filter { it.name.startsWith(prefix) }
|
||||
@@ -70,27 +94,26 @@ class IRUnusedCodeRemover(
|
||||
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
|
||||
val instructions = code.globalInits.instructions
|
||||
instructions.toTypedArray().forEach {ins ->
|
||||
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
|
||||
if(ins.referencedSymbol()?.startsWith(blockLabel)==true) {
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
|
||||
// remove stray loads
|
||||
val readRegs = mutableSetOf<VirtualRegister>()
|
||||
instructions.forEach { ins ->
|
||||
val readRegsCounts = mutableMapOf<VirtualRegister, Int>()
|
||||
val writeRegsCounts = mutableMapOf<VirtualRegister, Int>()
|
||||
val regsTypes = mutableMapOf<VirtualRegister, IRDataType>()
|
||||
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, null)
|
||||
readRegs.addAll(readRegsCounts.keys)
|
||||
}
|
||||
instructions.toTypedArray().forEach { ins ->
|
||||
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
|
||||
if(ins.reg1!=0) {
|
||||
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
else if(ins.fpReg1!=null) {
|
||||
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
val dest = ins.dest
|
||||
if(dest != null && dest.register !in readRegs) {
|
||||
ins.referencedSymbol()?.let { code.st.removeIfExists(it) }
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,8 +196,8 @@ class IRUnusedCodeRemover(
|
||||
// check if asmsub is linked or called from another regular subroutine
|
||||
irprog.foreachCodeChunk { chunk ->
|
||||
chunk.instructions.forEach {
|
||||
it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
|
||||
// note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
|
||||
it.referencedSymbol()?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
|
||||
// note: codeTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,10 +216,15 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
|
||||
private val entrypointNames = setOf("p8b_main.p8s_start", "main.start")
|
||||
private val mainBlockNames = setOf("p8b_main", "main")
|
||||
|
||||
|
||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||
val entrypointSub = irprog.blocks.single { it.label=="main" }
|
||||
.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val entrypointSub = irprog.blocks.single { it.label in mainBlockNames }
|
||||
.children.single { it is IRSubroutine && it.label in entrypointNames }
|
||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||
reachable.add(irprog.globalInits)
|
||||
|
||||
@@ -213,21 +241,35 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
}
|
||||
|
||||
// ... and likewise the ones referenced in struct instance initializers:
|
||||
structInstanceInitRefs()
|
||||
.filter { irprog.st.lookup(it.name) == null }
|
||||
.forEach { ref -> irprog.getChunkWithLabel(ref.name).let { reachable += it } }
|
||||
|
||||
fun grow() {
|
||||
val new = mutableSetOf<IRCodeChunkBase>()
|
||||
reachable.forEach {
|
||||
it.next?.let { next -> new += next }
|
||||
if (it is IRLoopChunk) {
|
||||
// A reachable counted loop makes every body chunk reachable too.
|
||||
// The body is no longer represented by next pointers in the outer list.
|
||||
new += it.body
|
||||
}
|
||||
it.instructions.forEach { instr ->
|
||||
if (instr.branchTarget == null)
|
||||
instr.labelSymbol?.let { label ->
|
||||
val codeTarget = instr.codeTarget
|
||||
if (codeTarget == null) {
|
||||
instr.referencedSymbol()?.let { label ->
|
||||
val chunk = allLabeledChunks[label] ?: allLabeledChunks[label.substringBeforeLast('.')]
|
||||
if(chunk!=null)
|
||||
new+=chunk
|
||||
else
|
||||
allLabeledChunks[label]?.let { c -> new += c }
|
||||
}
|
||||
else
|
||||
new += instr.branchTarget!!
|
||||
}
|
||||
else {
|
||||
val chunk = irprog.resolveCodeTarget(codeTarget)
|
||||
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
|
||||
if(chunk!=null)
|
||||
new += chunk
|
||||
}
|
||||
}
|
||||
}
|
||||
reachable += new
|
||||
@@ -260,24 +302,33 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
}
|
||||
|
||||
// ... and likewise the chunks referenced in struct instance initializers:
|
||||
structInstanceInitRefs()
|
||||
.filter { irprog.st.lookup(it.name) == null }
|
||||
.forEach { ref -> allLabeledChunks[ref.name]?.let { linkedChunks += it } }
|
||||
|
||||
irprog.foreachCodeChunk { chunk ->
|
||||
chunk.next?.let { next -> linkedChunks += next }
|
||||
chunk.instructions.forEach {
|
||||
if(it.branchTarget==null) {
|
||||
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||
val codeTarget = it.codeTarget
|
||||
if(codeTarget==null) {
|
||||
it.referencedSymbol()?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||
} else {
|
||||
linkedChunks += it.branchTarget!!
|
||||
val target = irprog.resolveCodeTarget(codeTarget)
|
||||
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
|
||||
if(target!=null)
|
||||
linkedChunks += target
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
if (chunk.label in entrypointNames)
|
||||
linkedChunks += chunk
|
||||
}
|
||||
|
||||
// make sure that chunks that are only used as a prefix of a label, are also marked as linked
|
||||
linkedChunks.toList().forEach { chunk ->
|
||||
chunk.instructions.forEach {
|
||||
if(it.labelSymbol!=null) {
|
||||
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
|
||||
it.referencedSymbol()?.let { symbol ->
|
||||
val chunkName = symbol.substringBeforeLast('.')
|
||||
allLabeledChunks[chunkName]?.let { c -> linkedChunks += c }
|
||||
}
|
||||
}
|
||||
@@ -290,6 +341,14 @@ class IRUnusedCodeRemover(
|
||||
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||
var numRemoved = 0
|
||||
irprog.foreachSub { sub ->
|
||||
// don't remove stuff in library modules or with %forceoutput
|
||||
// TODO this is needed to keep the subroutine body of sqrt_long from being wiped !??! Sounds like the bad solution to the problem: why is the *body* being cleared , making an empty sqrt_long subrotuine end up in the p8ir output (and eventually in the .asm, which crashes the resulting program)
|
||||
// TODO this causes other stuff to end up in the output IR that is not used......
|
||||
// note: this wasn't a problem on the virtual target, but it is a problem when using the newcodegen targets (because on the virtual target, the VM simply executes the SQRT instruction directly without needing a library routine)
|
||||
val block = irprog.blocks.first { b -> b.children.any { it === sub } }
|
||||
if (block.library || block.options.forceOutput)
|
||||
return@foreachSub
|
||||
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
@@ -301,6 +360,9 @@ class IRUnusedCodeRemover(
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
is IRLoopChunk -> {
|
||||
// keep loop at subroutine entry; don't remove
|
||||
}
|
||||
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
|
||||
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
|
||||
numRemoved++
|
||||
@@ -315,4 +377,4 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.intermediate.*
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* PROTOTYPE - NOT FOR PRODUCTION USE
|
||||
* ============================================================================
|
||||
*
|
||||
* This is a PROTOTYPE memory-slot packer and the intended reusable foundation
|
||||
* for a future hardware-register allocator targeting the m68k backend. It is
|
||||
* NOT a complete or production-ready register allocator in its current form.
|
||||
* It is DISABLED in production: its only call site in IRCodeGen.generate() is
|
||||
* commented out, and it currently runs only under TestRegisterPacker.
|
||||
*
|
||||
* What this prototype demonstrates:
|
||||
* - A working graph-coloring register allocator skeleton: CFG construction,
|
||||
* intraprocedural liveness analysis (gen/kill fixed-point), live interval
|
||||
* derivation, conflict-graph construction, greedy coloring, and IR rewrite.
|
||||
* - Coalescing of virtual registers into shared memory slots in the flat
|
||||
* `p8_regfile` BSS block (vregs with non-overlapping live ranges share a
|
||||
* slot).
|
||||
*
|
||||
* What this prototype DOES NOT do (and why it is not production-ready):
|
||||
* - No hardware-register allocation: slots are memory addresses in a flat
|
||||
* BSS regfile, not physical D/A/FP registers.
|
||||
* - No CALL clobbering: a callee packed into the same slot as a caller's
|
||||
* live value silently overwrites it (see bug 3 below).
|
||||
* - No register classes: no distinction between DATA, ADDRESS, and FPU
|
||||
* registers (required by the m68k backend).
|
||||
* - No spilling, prologue/epilogue emission, or CALL-aware interference.
|
||||
* - Known-unsound liveness for complex control flow (nested loops,
|
||||
* conditionals, early returns) - see bug 2 below.
|
||||
*
|
||||
* Intended reuse path (per ideas/m68k-register-allocation.md):
|
||||
* - Stage 1: Make the m68k backend's instruction selection location-agnostic
|
||||
* (operand() indirection returning "d3" if allocated, "p8_regfile+N" if
|
||||
* spilled) - prerequisite bulk work, no behavior change.
|
||||
* - Stage 2: Replace this packer's interference model with a correct one
|
||||
* (CALL-aware, class-constrained) and point the coloring step at real
|
||||
* D2-D6 / A2-A4 / FP2-FP7 registers. Six open design decisions remain
|
||||
* in Stage 2 (see design doc section 7.1).
|
||||
* - Stage 3: Delete subsumed peephole optimizations, update test assertions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Known bugs / unsoundness (carried over from the removed register-packing.md):
|
||||
*
|
||||
* 1. Disjoint-interval value clobbering (observed on cx16 Fibonacci).
|
||||
* A register with multiple DISJOINT live ranges (e.g. a loop counter written
|
||||
* at loop entry and read at loop exit, with no uses in between) was split
|
||||
* into separate intervals; the packer allowed another register to share the
|
||||
* same slot during the gap, overwriting the value before its next read.
|
||||
* Fix applied (see packSubroutine interval merging): always merge ALL
|
||||
* intervals of the same register into one contiguous range, regardless of
|
||||
* overlap, so the value persists across gaps.
|
||||
*
|
||||
* 2. Complex control flow (observed on m68k TextElite).
|
||||
* The iterative gen/kill fixed-point liveness (computeLiveness) produced
|
||||
* incorrect live ranges for subroutines with complex CFGs (nested
|
||||
* conditionals, loops, early returns). Two registers with genuinely
|
||||
* overlapping live ranges were assigned the same slot, so one clobbered the
|
||||
* other at runtime (symptom: infinite loop printing spaces — galaxy map
|
||||
* data read from the wrong memory location). Root cause: the dataflow does
|
||||
* not converge to the true meet-over-all-paths solution for all loop /
|
||||
* conditional structures. The liveness MUST be validated against nested
|
||||
* loops, conditionals, early returns, and switch-like dispatch before reuse.
|
||||
*
|
||||
* 3. Cross-subroutine slot collision (fundamental design issue; see header
|
||||
* comment above). Per-subroutine packing into ONE flat shared regfile is
|
||||
* unsound: a callee packed into the same slot as a caller's live value
|
||||
* clobbers it. The packer does not model CALL clobbering. Soundness would
|
||||
* require the full-program call tree (call-graph-aware allocation, depth
|
||||
* ranges, or save/restore around calls) — which is exactly what the m68k
|
||||
* allocator with a calling convention avoids by allocating per-subroutine.
|
||||
*
|
||||
* 4. Interval-merge type loss / silent skips. When merging a register's
|
||||
* intervals, the merged interval keeps only the FIRST interval's type; if a
|
||||
* later interval has an incompatible type, the register is dropped via
|
||||
* skipRegs (and its original number is preserved via the startSlot =
|
||||
* maxReg + 1 invariant). When reusing this code, merged intervals must carry
|
||||
* the WIDEST type, not just the first, to avoid under-sized slots.
|
||||
*/
|
||||
|
||||
object RegisterPacker {
|
||||
|
||||
data class Interval(val register: VirtualRegister, val start: Int, val end: Int, val type: IRDataType)
|
||||
|
||||
fun pack(irProg: IRProgram) {
|
||||
val allRegTypes = mutableMapOf<VirtualRegister, IRDataType>()
|
||||
irProg.forEachInstruction { instr ->
|
||||
for (access in instr.registerAccesses)
|
||||
allRegTypes.putIfAbsent(access.register, access.type)
|
||||
}
|
||||
val beforeCount = allRegTypes.size
|
||||
|
||||
// Start packing slots after the highest original register number to avoid
|
||||
// collisions between packed slot numbers and original (non-packed) register numbers.
|
||||
val maxReg = allRegTypes.keys.maxOfOrNull { it.num } ?: 0
|
||||
val startSlot = maxReg + 1
|
||||
val globalSlotTypes = mutableMapOf<VirtualRegister, IRDataType>()
|
||||
irProg.foreachSub { sub -> packSubroutine(irProg, sub, allRegTypes, globalSlotTypes, startSlot) }
|
||||
|
||||
val afterTypes = rebuildTypeMap(irProg)
|
||||
val afterCount = afterTypes.size
|
||||
if (!irProg.options.quiet)
|
||||
println("Register packing: $beforeCount -> $afterCount registers")
|
||||
}
|
||||
|
||||
// Rebuild the register type map after packing, using the same type determination as the packer.
|
||||
// This avoids the strict per-instruction validation in usedRegisters() that POINTER↔LONG↔WORD
|
||||
// cross-type packing can trigger.
|
||||
fun rebuildTypeMap(irProg: IRProgram): Map<VirtualRegister, IRDataType> {
|
||||
val newTypes = mutableMapOf<VirtualRegister, IRDataType>()
|
||||
irProg.forEachInstruction { instr ->
|
||||
for (access in instr.registerAccesses)
|
||||
newTypes.putIfAbsent(access.register, access.type)
|
||||
}
|
||||
return newTypes
|
||||
}
|
||||
|
||||
private fun packSubroutine(irProg: IRProgram, sub: IRSubroutine, allRegTypes: Map<VirtualRegister, IRDataType>, globalSlotTypes: MutableMap<VirtualRegister, IRDataType>, startSlot: Int) {
|
||||
if (sub.chunks.isEmpty())
|
||||
return
|
||||
|
||||
val successors = buildCFG(sub, irProg)
|
||||
val (_, liveOut) = computeLiveness(sub, successors)
|
||||
|
||||
val codeChunks = sub.chunks.filterIsInstance<IRCodeChunk>()
|
||||
if (codeChunks.isEmpty())
|
||||
return
|
||||
|
||||
val chunkRanges = mutableMapOf<IRCodeChunk, IntRange>()
|
||||
var globalIdx = 0
|
||||
for (chunk in codeChunks) {
|
||||
val start = globalIdx
|
||||
val end = globalIdx + chunk.instructions.size - 1
|
||||
chunkRanges[chunk] = start..end
|
||||
globalIdx += chunk.instructions.size
|
||||
}
|
||||
|
||||
val registerIntervals = mutableMapOf<VirtualRegister, MutableList<Interval>>()
|
||||
val registerTypes = mutableMapOf<VirtualRegister, IRDataType>()
|
||||
|
||||
for (chunk in codeChunks) {
|
||||
val range = chunkRanges[chunk]!!
|
||||
val chunkStart = range.first
|
||||
if (chunk.instructions.isEmpty())
|
||||
continue
|
||||
|
||||
val liveSet = liveOut[chunk]?.toMutableSet() ?: mutableSetOf()
|
||||
val lastUse = mutableMapOf<VirtualRegister, Int>()
|
||||
|
||||
// Scan backward through instructions
|
||||
for (i in chunk.instructions.indices.reversed()) {
|
||||
val instr = chunk.instructions[i]
|
||||
val globalI = chunkStart + i
|
||||
val accesses = instr.registerAccesses
|
||||
|
||||
// Process READ (USE / USE_DEF) first so types are set before WRITE processing
|
||||
for (access in accesses) {
|
||||
if (access.direction != OperandDirection.DEF) {
|
||||
val r = access.register
|
||||
registerTypes.putIfAbsent(r, access.type)
|
||||
if (r !in liveSet) {
|
||||
lastUse[r] = globalI
|
||||
liveSet.add(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process WRITTEN (DEF / USE_DEF) registers: end their live range (start of interval going forward)
|
||||
for (access in accesses) {
|
||||
if (access.direction != OperandDirection.USE) {
|
||||
val r = access.register
|
||||
if (r in liveSet) {
|
||||
val end = lastUse.getOrElse(r) { globalI }
|
||||
registerIntervals.getOrPut(r) { mutableListOf() }
|
||||
.add(Interval(r, globalI, end, access.type))
|
||||
liveSet.remove(r)
|
||||
lastUse.remove(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Registers still live at start of chunk are live-in
|
||||
for (r in liveSet) {
|
||||
val end = lastUse.getOrElse(r) { chunkStart }
|
||||
registerIntervals.getOrPut(r) { mutableListOf() }
|
||||
.add(Interval(r, chunkStart, end, registerTypes.getOrElse(r) { IRDataType.BYTE }))
|
||||
}
|
||||
}
|
||||
|
||||
if (registerIntervals.isEmpty())
|
||||
return
|
||||
|
||||
// Merge adjacent/overlapping intervals for each register.
|
||||
// Also merge non-overlapping intervals of the same register into a single
|
||||
// contiguous range, because the value in the slot must persist across the
|
||||
// gap to the next use (otherwise another register packed to the same slot
|
||||
// would clobber it between intervals).
|
||||
val mergedIntervals = mutableListOf<Interval>()
|
||||
val skipRegs = mutableSetOf<VirtualRegister>()
|
||||
for ((reg, intervals) in registerIntervals) {
|
||||
val sorted = intervals.sortedBy { it.start }
|
||||
var current = sorted.first()
|
||||
for (next in sorted.drop(1)) {
|
||||
if (current.type != next.type && !typesCompatible(current.type, next.type)) {
|
||||
skipRegs.add(reg)
|
||||
}
|
||||
// Always merge: same register's intervals are merged into a single
|
||||
// contiguous range to preserve the value across gaps.
|
||||
current = Interval(reg, current.start, maxOf(current.end, next.end), current.type)
|
||||
}
|
||||
mergedIntervals.add(current)
|
||||
}
|
||||
if (skipRegs.isNotEmpty()) {
|
||||
mergedIntervals.removeAll { it.register in skipRegs }
|
||||
}
|
||||
|
||||
val conflictGraph = buildConflictGraph(sub)
|
||||
val packing = greedyColor(mergedIntervals, conflictGraph, allRegTypes, globalSlotTypes, startSlot)
|
||||
|
||||
if (packing.isNotEmpty())
|
||||
rewrite(sub, packing)
|
||||
}
|
||||
|
||||
private fun buildConflictGraph(sub: IRSubroutine): Map<VirtualRegister, Set<VirtualRegister>> {
|
||||
val conflicts = mutableMapOf<VirtualRegister, MutableSet<VirtualRegister>>()
|
||||
for (chunk in sub.chunks.filterIsInstance<IRCodeChunk>()) {
|
||||
val typeRegs = mutableMapOf<IRDataType, MutableSet<VirtualRegister>>()
|
||||
for (instr in chunk.instructions) {
|
||||
val accesses = instr.registerAccesses
|
||||
for (access in accesses)
|
||||
typeRegs.getOrPut(access.type) { mutableSetOf() }.add(access.register)
|
||||
|
||||
// Registers accessed together within a single instruction must not share a slot
|
||||
// (generalizes the old reg1/reg2-only conflict rule to every register access of
|
||||
// the instruction, now that they are all enumerable via registerAccesses).
|
||||
val regsInInstr = accesses.map { it.register }.distinct()
|
||||
for (i in regsInInstr.indices) {
|
||||
for (j in i + 1 until regsInInstr.size) {
|
||||
val r1 = regsInInstr[i]
|
||||
val r2 = regsInInstr[j]
|
||||
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
|
||||
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add conflicts between registers with incompatible types in the same chunk
|
||||
val typeList = typeRegs.entries.toList()
|
||||
for (i in typeList.indices) {
|
||||
for (j in i + 1 until typeList.size) {
|
||||
val (t1, regs1) = typeList[i]
|
||||
val (t2, regs2) = typeList[j]
|
||||
if (t1 != t2 && !typesCompatible(t1, t2)) {
|
||||
for (r1 in regs1) {
|
||||
for (r2 in regs2) {
|
||||
if (r1 != r2) {
|
||||
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
|
||||
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return conflicts
|
||||
}
|
||||
|
||||
private fun buildCFG(sub: IRSubroutine, irProg: IRProgram): Map<IRCodeChunkBase, List<IRCodeChunkBase>> {
|
||||
val successors = mutableMapOf<IRCodeChunkBase, MutableList<IRCodeChunkBase>>()
|
||||
|
||||
val conditionals = setOf(
|
||||
Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTEQ, Opcode.BSTNE,
|
||||
Opcode.BSTNEG, Opcode.BSTPOS, Opcode.BSTVC, Opcode.BSTVS,
|
||||
Opcode.BGTR, Opcode.BGT, Opcode.BLT,
|
||||
Opcode.BGTSR, Opcode.BGTS, Opcode.BLTS,
|
||||
Opcode.BGER, Opcode.BGE, Opcode.BLE,
|
||||
Opcode.BGESR, Opcode.BGES, Opcode.BLES
|
||||
)
|
||||
|
||||
for (chunk in sub.chunks) {
|
||||
val succ = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
if (chunk is IRCodeChunk) {
|
||||
val lastInstr = chunk.instructions.lastOrNull()
|
||||
val endsWithUnconditional = lastInstr != null && lastInstr.opcode in OpcodesThatBranchUnconditionally
|
||||
val endsWithConditional = lastInstr != null && lastInstr.opcode in conditionals
|
||||
|
||||
if (!endsWithUnconditional) {
|
||||
chunk.next?.let { succ.add(it) }
|
||||
}
|
||||
if (endsWithConditional) {
|
||||
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { target ->
|
||||
if (!succ.contains(target))
|
||||
succ.add(target)
|
||||
}
|
||||
} else if (lastInstr?.opcode == Opcode.JUMP) {
|
||||
succ.clear()
|
||||
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { succ.add(it) }
|
||||
}
|
||||
} else {
|
||||
chunk.next?.let { succ.add(it) }
|
||||
}
|
||||
|
||||
successors[chunk] = succ
|
||||
}
|
||||
|
||||
return successors
|
||||
}
|
||||
|
||||
private fun computeLiveness(
|
||||
sub: IRSubroutine,
|
||||
successors: Map<IRCodeChunkBase, List<IRCodeChunkBase>>
|
||||
): Pair<Map<IRCodeChunk, Set<VirtualRegister>>, Map<IRCodeChunk, Set<VirtualRegister>>> {
|
||||
|
||||
val liveIn = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
|
||||
val liveOut = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
|
||||
val gen = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
|
||||
val kill = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
|
||||
|
||||
for (chunk in sub.chunks) {
|
||||
if (chunk !is IRCodeChunk)
|
||||
continue
|
||||
|
||||
val genSet = mutableSetOf<VirtualRegister>()
|
||||
val killSet = mutableSetOf<VirtualRegister>()
|
||||
|
||||
for (instr in chunk.instructions) {
|
||||
val written = instr.definitions
|
||||
val read = instr.uses
|
||||
for (r in read) {
|
||||
if (r !in killSet)
|
||||
genSet.add(r)
|
||||
}
|
||||
for (r in written) {
|
||||
killSet.add(r)
|
||||
}
|
||||
}
|
||||
|
||||
gen[chunk] = genSet
|
||||
kill[chunk] = killSet
|
||||
liveIn[chunk] = mutableSetOf()
|
||||
liveOut[chunk] = mutableSetOf()
|
||||
}
|
||||
|
||||
var changed = true
|
||||
|
||||
while (changed) {
|
||||
changed = false
|
||||
|
||||
for (chunk in sub.chunks.reversed()) {
|
||||
if (chunk !is IRCodeChunk)
|
||||
continue
|
||||
|
||||
val newLiveOut = mutableSetOf<VirtualRegister>()
|
||||
for (succ in successors[chunk].orEmpty()) {
|
||||
if (succ is IRCodeChunk)
|
||||
newLiveOut.addAll(liveIn[succ].orEmpty())
|
||||
}
|
||||
|
||||
if (newLiveOut != liveOut[chunk]) {
|
||||
liveOut[chunk] = newLiveOut
|
||||
changed = true
|
||||
}
|
||||
|
||||
val newLiveIn = mutableSetOf<VirtualRegister>()
|
||||
newLiveIn.addAll(gen[chunk].orEmpty())
|
||||
newLiveIn.addAll(liveOut[chunk].orEmpty() - kill[chunk].orEmpty())
|
||||
|
||||
if (newLiveIn != liveIn[chunk]) {
|
||||
liveIn[chunk] = newLiveIn
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return liveIn to liveOut
|
||||
}
|
||||
|
||||
private fun greedyColor(
|
||||
intervals: List<Interval>,
|
||||
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
|
||||
allRegTypes: Map<VirtualRegister, IRDataType>,
|
||||
slotTypes: MutableMap<VirtualRegister, IRDataType>,
|
||||
startSlot: Int
|
||||
): Map<VirtualRegister, VirtualRegister> {
|
||||
if (intervals.isEmpty())
|
||||
return emptyMap()
|
||||
|
||||
val packing = mutableMapOf<VirtualRegister, VirtualRegister>()
|
||||
// Reserve slots for non-packed registers
|
||||
val packedRegs = intervals.map { it.register }.toSet()
|
||||
for ((reg, type) in allRegTypes) {
|
||||
if (reg !in packedRegs)
|
||||
slotTypes.putIfAbsent(reg, type)
|
||||
}
|
||||
|
||||
val sorted = intervals.sortedWith(compareBy<Interval> { it.type.ordinal }.thenBy { it.start })
|
||||
val activeSlots = mutableMapOf<VirtualRegister, Pair<VirtualRegister, Int>>()
|
||||
|
||||
for (interval in sorted) {
|
||||
activeSlots.entries.removeAll { it.value.second < interval.start }
|
||||
|
||||
var slotNum = startSlot
|
||||
var slot: VirtualRegister
|
||||
while (true) {
|
||||
// a packed slot stays in the same register file (int/float) as the register it replaces
|
||||
slot = if (interval.register is VirtualRegister.FloatReg) VirtualRegister.float(slotNum) else VirtualRegister.int(slotNum)
|
||||
if (slot !in activeSlots) {
|
||||
val existingType = slotTypes[slot]
|
||||
if (existingType == null || existingType == interval.type || typesCompatible(existingType, interval.type)) {
|
||||
if (!conflictsWithRegister(slot, interval.register, conflictGraph, packing))
|
||||
break
|
||||
}
|
||||
}
|
||||
slotNum++
|
||||
}
|
||||
|
||||
packing[interval.register] = slot
|
||||
slotTypes.putIfAbsent(slot, interval.type) // keep the first-assigned type to prevent type narrowing via typesCompatible chain
|
||||
activeSlots[slot] = Pair(interval.register, interval.end)
|
||||
}
|
||||
return packing
|
||||
}
|
||||
|
||||
private fun typesCompatible(t1: IRDataType, t2: IRDataType): Boolean {
|
||||
return t1 == t2 || (t1 == IRDataType.POINTER && t2 in setOf(IRDataType.WORD, IRDataType.LONG)) ||
|
||||
(t2 == IRDataType.POINTER && t1 in setOf(IRDataType.WORD, IRDataType.LONG))
|
||||
}
|
||||
|
||||
private fun conflictsWithRegister(
|
||||
slot: VirtualRegister,
|
||||
register: VirtualRegister,
|
||||
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
|
||||
packing: Map<VirtualRegister, VirtualRegister>
|
||||
): Boolean {
|
||||
val conflictingRegs = conflictGraph[register] ?: return false
|
||||
for (cr in conflictingRegs) {
|
||||
val crSlot = packing[cr]
|
||||
if (crSlot != null && crSlot == slot)
|
||||
return true
|
||||
if (cr !in packing && cr == slot)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun rewrite(sub: IRSubroutine, packing: Map<VirtualRegister, VirtualRegister>) {
|
||||
for (chunk in sub.chunks) {
|
||||
if (chunk !is IRCodeChunk)
|
||||
continue
|
||||
|
||||
for (i in chunk.instructions.indices) {
|
||||
val instr = chunk.instructions[i]
|
||||
val newInstr = instr.mapRegisters { vr -> packing[vr] ?: vr }
|
||||
if (newInstr != instr)
|
||||
chunk.instructions[i] = newInstr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,11 @@ internal class RegisterPool {
|
||||
private val registerTypes: MutableMap<RegisterNum, IRDataType> = mutableMapOf()
|
||||
|
||||
fun getTypes(): Map<RegisterNum, IRDataType> = registerTypes
|
||||
fun resetTypes(types: Map<RegisterNum, IRDataType>) {
|
||||
// called after register packing
|
||||
registerTypes.clear()
|
||||
registerTypes.putAll(types)
|
||||
}
|
||||
|
||||
init {
|
||||
for(i in 99000..99099)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.ast.PtStructField
|
||||
import prog8.code.core.Position
|
||||
import prog8.intermediate.*
|
||||
|
||||
/**
|
||||
@@ -10,7 +11,10 @@ import prog8.intermediate.*
|
||||
*/
|
||||
private class StToIrConverter(val romable: Boolean) {
|
||||
fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
IRStStructDef(struct.scopedNameString, makeIrStructFieldsFrom(struct.fields), struct.size, struct.isUnion)
|
||||
|
||||
private fun makeIrStructFieldsFrom(fields: List<PtStructField>): List<IRStStructField> =
|
||||
fields.map { IRStStructField(it.type, it.name, it.arraySize) }
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
|
||||
return when(elt) {
|
||||
@@ -73,9 +77,9 @@ private class StToIrConverter(val romable: Boolean) {
|
||||
|
||||
val name = if('.' in variable.name) variable.name else variable.scopedNameString
|
||||
val hasInit = initValue != null
|
||||
val inBss = romable && hasInit
|
||||
val inBss = !hasInit || romable
|
||||
val readonly = romable && hasInit
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly)
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly, variable.shared, variable.astNode?.position ?: Position.DUMMY)
|
||||
}
|
||||
|
||||
fun convert(variable: StMemVar): IRStMemVar {
|
||||
@@ -108,13 +112,14 @@ private class StToIrConverter(val romable: Boolean) {
|
||||
constant.name
|
||||
}
|
||||
}
|
||||
val noPrefix = constant.astNode?.definingBlock()?.options?.noSymbolPrefixing == true
|
||||
if(constant.value != null)
|
||||
return IRStConstant(scopedName, constant.dt, constant.value)
|
||||
return IRStConstant(scopedName, constant.dt, constant.value, noPrefix = noPrefix)
|
||||
else if(constant.memorySlab != null) {
|
||||
// For memory() constants, store reference to slab name
|
||||
// The actual address will be resolved during code generation
|
||||
val slabName = constant.memorySlab!!.name
|
||||
return IRStConstant(scopedName, constant.dt, null, slabName)
|
||||
return IRStConstant(scopedName, constant.dt, null, slabName, noPrefix)
|
||||
}
|
||||
else
|
||||
TODO("constant without value or memory slab: $constant")
|
||||
@@ -127,10 +132,17 @@ private class StToIrConverter(val romable: Boolean) {
|
||||
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
|
||||
}
|
||||
|
||||
fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
|
||||
val values = fields.zip(instance.initialValues).map { (field, value) ->
|
||||
fun convert(instance: StStructInstance, fields: Iterable<PtStructField>): IRStStructInstance {
|
||||
val expanded = fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
if(arraySz!=null)
|
||||
List(arraySz) { field.type.sub!! }
|
||||
else
|
||||
listOf(field.type.base)
|
||||
}
|
||||
val values = expanded.zip(instance.initialValues).map { (baseType, value) ->
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
IRStructInitValue(baseType, elt)
|
||||
}
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 703 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user