mirror of
https://github.com/irmen/prog8.git
synced 2025-06-17 16:23:42 +00:00
Compare commits
958 Commits
Author | SHA1 | Date | |
---|---|---|---|
0f83dc6491 | |||
cc22861719 | |||
a14c192ea3 | |||
b3d98be862 | |||
43027a4728 | |||
03831a7394 | |||
fdbbd181ea | |||
69075376dc | |||
504d1440cc | |||
9e33b8b8da | |||
0cfcc5cd29 | |||
e0de662f8e | |||
66a836d094 | |||
80095f4962 | |||
828d83dbef | |||
7de665d1e4 | |||
0a356ba73a | |||
41de8caa13 | |||
968609d06d | |||
3b199a2a87 | |||
0c1018ec61 | |||
bc3f2db3de | |||
06bedb7adb | |||
45a9751217 | |||
e8da62aa29 | |||
ddb2ff4216 | |||
f27e3478b9 | |||
38dc7fb7bd | |||
aa4cd13c31 | |||
4a4b6c04a1 | |||
37fa3b34a2 | |||
f8084e7955 | |||
4d5119ce3e | |||
d85c347a6c | |||
7dd758a753 | |||
806654fc44 | |||
8e6b91cb9e | |||
334e6dca28 | |||
f2daa17b92 | |||
6d9fccacb1 | |||
37638e7ed0 | |||
8a0e650511 | |||
8ba5a0d90c | |||
bfd3edb617 | |||
56ba24962c | |||
19a2110ba2 | |||
242a3eec63 | |||
fee46f2e54 | |||
6aed7e429a | |||
517ea82b99 | |||
99c29343de | |||
892fa76883 | |||
d446b57d05 | |||
0e086d788b | |||
498841d45d | |||
d1f8ee1e56 | |||
07feb5c925 | |||
75fd263e85 | |||
1e1f444cab | |||
89cc7e5fa9 | |||
265e7aefbf | |||
1c55a6c6dc | |||
8f18b5b8a7 | |||
f790182f0b | |||
813007a5d8 | |||
d03ff1e4d0 | |||
932bbd0381 | |||
01bd648cb2 | |||
779a5606a7 | |||
ccc11e49d2 | |||
d28c994ecd | |||
5d88717f32 | |||
e35cfd4971 | |||
bcc4bf5c2b | |||
a0594cbce3 | |||
078bfefe41 | |||
9c1b11d605 | |||
44d82f9190 | |||
37fcde30d6 | |||
09c6cb4d6b | |||
b428343c2a | |||
dfce292294 | |||
2b8f613a00 | |||
2eb137618e | |||
4bb2b8ca9b | |||
5179562fb2 | |||
0a4de45453 | |||
ffdc658dc8 | |||
7530f4407b | |||
73864c8101 | |||
f948917124 | |||
0d44492086 | |||
38a22fbc99 | |||
8ae435549d | |||
9b113c0cbb | |||
0e0fac8c4b | |||
4cd9bb8f99 | |||
ad9eaeafeb | |||
6cd392909c | |||
49ec430592 | |||
09f3fbeb38 | |||
e7698686fa | |||
66d939df0d | |||
6bc079c7b7 | |||
299419917e | |||
69f6afe420 | |||
b7279a3d9e | |||
e14b854d7b | |||
8bd7c601c0 | |||
997288fa03 | |||
0f26b39997 | |||
ae66fcac1e | |||
43944a94eb | |||
eba0bde6f3 | |||
4544af441b | |||
a8be94de6b | |||
b24df31c2b | |||
332ba8ed7e | |||
58400f53bc | |||
01c2112881 | |||
a546c2247d | |||
0da9142009 | |||
796add0ee2 | |||
00b32f64e6 | |||
f97b3f23e2 | |||
08a079a96e | |||
e98e951834 | |||
2668bf8519 | |||
dd4c073e18 | |||
c7c72f00c7 | |||
ef1c665b9a | |||
d56565be25 | |||
e076b3aedc | |||
ae3b2ddf5f | |||
1bdc427d73 | |||
6a639ce533 | |||
d91ca8b197 | |||
a01c0a283d | |||
5c393091a0 | |||
01b680504b | |||
8e4319cd5a | |||
5a776dd690 | |||
cce08d95db | |||
28c1b208c1 | |||
3844bf1f72 | |||
745d192563 | |||
ee782e92ac | |||
afbc91d1fc | |||
f998888d6d | |||
7d8b42d63e | |||
6ebd4e821f | |||
d1806bfdc3 | |||
1d2d7155da | |||
b09e0a05bf | |||
c609e982fe | |||
2b227b43fe | |||
48f09f71ab | |||
ead8c59bda | |||
db52a9466c | |||
1509de390e | |||
88a1aa4f3d | |||
172e78e8f2 | |||
36bfef567d | |||
e40ebd75a2 | |||
992732f2cb | |||
b58a3ba1bb | |||
afe521b0c9 | |||
5d9caef45f | |||
278e2f5605 | |||
1e299bf360 | |||
8dfa0bc38c | |||
fde136fb7b | |||
ee4da1a757 | |||
ae2d96c455 | |||
6d8fbe0877 | |||
2fa1d8f2e8 | |||
533090a68e | |||
1dff59e1d6 | |||
44d232f52a | |||
5f6cff739a | |||
2764d235a9 | |||
45debff89f | |||
c45fbe6310 | |||
9ef9c24388 | |||
6a40f23578 | |||
6a0a6b4751 | |||
0bee6f6b41 | |||
82a15b5a16 | |||
11b7c4459e | |||
98570ac456 | |||
1b2296ad5b | |||
16851746d6 | |||
935450a45f | |||
ba67fd318b | |||
08ac459a41 | |||
a83e9d9a0a | |||
62d3f01948 | |||
af5ca2d0b8 | |||
ab4bcdf12d | |||
a6756d2cea | |||
f81061dd42 | |||
8e2c304b3c | |||
f21adaa3ef | |||
2637939e62 | |||
faf05582f8 | |||
161c02ced3 | |||
ff8de8e42d | |||
09d506194f | |||
42db3085df | |||
ad14c88fde | |||
0c9daf6eaf | |||
86c6530e46 | |||
159f80d629 | |||
aa949165c7 | |||
d22359b6e7 | |||
d73709653d | |||
405926e811 | |||
36758f41a4 | |||
7ebc9c79cf | |||
e0668b55b9 | |||
76c09da961 | |||
7e3b8c2c59 | |||
ecca854c7c | |||
3b0d7ea960 | |||
f70fa42eac | |||
5698de6cf4 | |||
c5a333a904 | |||
ff324955dd | |||
70436f5dca | |||
31177a2b1b | |||
4de012fc49 | |||
ee2888e744 | |||
efe4df92dc | |||
723ab54f97 | |||
d9389afc66 | |||
e7178ee496 | |||
d5f35bb3fb | |||
72f1a779f2 | |||
3277544295 | |||
98d2c64d5d | |||
f68b46fc60 | |||
d54ab856e7 | |||
16b24fadea | |||
b3803cbdf1 | |||
2ceaa25181 | |||
513611c5a6 | |||
7ec4ba40ad | |||
92374e122b | |||
94f12732ab | |||
0904712a00 | |||
32becdbced | |||
34aa21f7d9 | |||
cc81dd7d3e | |||
335213b55f | |||
13ab4166c0 | |||
3dc5a0e7f8 | |||
e15c5cde53 | |||
d88c09b098 | |||
893b383bdf | |||
dd7c9d62e6 | |||
97c5c90eff | |||
1fb94e7a7b | |||
daca87c6d0 | |||
203ec5fa46 | |||
9ea69c07b8 | |||
68539d6cc9 | |||
f75fd0811e | |||
836bc9d456 | |||
a37769aafe | |||
68e62e4bd2 | |||
a5cd3728c9 | |||
a48ce35f0b | |||
e1835b5775 | |||
433832b329 | |||
ee81da14d6 | |||
6395d1908e | |||
989a5a2f8a | |||
b7a622c68e | |||
a8507b437d | |||
e505bf9ccf | |||
a289b32053 | |||
c3f1f09ad1 | |||
70ee2026ff | |||
690782bf60 | |||
755cc4835e | |||
a684ea46e4 | |||
8fbe13f99d | |||
452e9e275f | |||
cd40088636 | |||
9b9e6f4af5 | |||
ae6eeadf54 | |||
5268b05060 | |||
390263a34e | |||
55646edc3e | |||
8d177beb78 | |||
1da0c59182 | |||
36e8f10d2b | |||
cdf5a8f20f | |||
eb64d92333 | |||
eb55da63ef | |||
918302f79b | |||
9d7131d9f6 | |||
229c1114dd | |||
885df9156f | |||
c319233ddc | |||
958b5c0780 | |||
880c0a5da8 | |||
237c6dc856 | |||
ccf6e32bf9 | |||
a1874f6f00 | |||
95e4490a8a | |||
31c132c2eb | |||
00b0ec58b4 | |||
a1d0e5bb65 | |||
03e0d4b2e8 | |||
6afdd4e6fd | |||
b500a0d477 | |||
dd2463a440 | |||
23a8bebd9e | |||
3caf9108ad | |||
bde4be8231 | |||
0bbbb12ed2 | |||
b570bdaed7 | |||
8c0843cc87 | |||
31458ffd81 | |||
c15c10a94e | |||
9fca978725 | |||
b125901717 | |||
eb018ae660 | |||
7e5a9474fe | |||
525a9b5036 | |||
c3fbdf34ca | |||
48bd51e1a5 | |||
10d0b03a90 | |||
e1b3582f08 | |||
95be1c9e22 | |||
1ce8fe06d5 | |||
15c649024e | |||
e97303c226 | |||
3b786c819d | |||
04959dbd8b | |||
5cd4b874ea | |||
f14ea1b3de | |||
9cc0cda0fb | |||
09a7a4bbe5 | |||
cfea8b3745 | |||
28bf0b61ce | |||
2dc2429735 | |||
83d4592526 | |||
2d528c26ae | |||
66b3dce794 | |||
93f77a1045 | |||
aa4d23a3d5 | |||
2d7ebff8e9 | |||
bad9dd3b3b | |||
2f4e517857 | |||
ff35ba3696 | |||
72768e7fad | |||
77f3852cdc | |||
66857ca477 | |||
75514fc7af | |||
be06d871b6 | |||
f98ee326b4 | |||
bc8126eb16 | |||
4c8beefdcb | |||
bbb6c53457 | |||
d8991894e3 | |||
c7b7dcfd03 | |||
2c9e50873c | |||
923367296d | |||
151a206617 | |||
e403c4cf99 | |||
e3fbe37f9f | |||
dc870cd5ea | |||
584be44743 | |||
5fffd35ec1 | |||
b92e22e4a6 | |||
3e6d16a7a8 | |||
ecbcc277b8 | |||
dff1d9e4dd | |||
7c0bde7310 | |||
a82d21ac05 | |||
0bf8378fcb | |||
017ef8a837 | |||
0d63cdcb96 | |||
68a6f99c9f | |||
60781bcfc4 | |||
77fa2e2722 | |||
c36afd872e | |||
7e58a4c130 | |||
19a4bf1088 | |||
9678bbae4b | |||
a4d093afa1 | |||
ba788bcf0f | |||
f2c62bee7e | |||
548721e306 | |||
1ae950a638 | |||
c9385e93fe | |||
9bb16e293c | |||
c223702ea0 | |||
9167ba499d | |||
2d7e95e1b6 | |||
0cba736446 | |||
0816a57032 | |||
a0ab0bd3e2 | |||
b89ad4b328 | |||
6cda76a116 | |||
c112b327ab | |||
46c12a8899 | |||
c5219dfb3f | |||
4a8ee6815a | |||
e1b6bb154a | |||
b19c282269 | |||
e520921746 | |||
970642244b | |||
3b90be2d9e | |||
2f756f1e3a | |||
78e84182f0 | |||
65a7a8caf8 | |||
4c6a2f5df9 | |||
fea297e409 | |||
7cf6aba625 | |||
3bbc00cc8c | |||
70ed2b4203 | |||
0adce9b9c6 | |||
0e781d18fa | |||
4575a8fffe | |||
10d0ff252b | |||
c7d54570cc | |||
7136b33f2e | |||
70a78e74f6 | |||
d5707b7bf3 | |||
9f247901d4 | |||
5659742d97 | |||
450eaf7c4a | |||
47485e4b49 | |||
64254e758d | |||
c1aa5d4e47 | |||
ab8173637a | |||
3841cef497 | |||
b717f1c7eb | |||
da57f76de3 | |||
4784f1c65a | |||
41af63b333 | |||
e2bb0de24d | |||
b791fae9ce | |||
6033a9e20c | |||
9e8c8973d8 | |||
3933bf5c1a | |||
708e296774 | |||
84925ab69c | |||
b3cb9b7fe2 | |||
9cb61fa34d | |||
7c219d235c | |||
6938c79f88 | |||
b8284a147d | |||
15ee90e99c | |||
795f80b4ec | |||
6b6427492d | |||
6055b8c3dc | |||
a98cb50d55 | |||
e98bbc1c52 | |||
7245aece4f | |||
60cbb02822 | |||
4e863ecdac | |||
5037033fcf | |||
e6b158bc97 | |||
4cc0dfa10b | |||
4ced8889d3 | |||
d26967a87d | |||
fc8955941b | |||
071a80360f | |||
d2154f5f2e | |||
334d382bfa | |||
90c4b00f74 | |||
71261525e8 | |||
3126959576 | |||
02e51d8282 | |||
ffb2027a19 | |||
70c9ab9074 | |||
6d1fdf1ba6 | |||
1f7180d9a8 | |||
b4e94ae4dd | |||
07c606bfc9 | |||
e705a8bd89 | |||
b3bdfb7f1f | |||
5af1aeb092 | |||
be64fa674a | |||
204f5591a9 | |||
ee3e3a3a40 | |||
f9200a2b75 | |||
f570b70827 | |||
0db141eeac | |||
acb2ee53bb | |||
c544b7f5ba | |||
c0024e97e5 | |||
bdf8aa9168 | |||
de5ce0f515 | |||
bb95484c8a | |||
cad18b8a3a | |||
0f6a98751a | |||
aac5a4c27f | |||
d3f6415387 | |||
04da44eb98 | |||
7649be97b1 | |||
c9ef777e0f | |||
c0cb2438d5 | |||
30c531b39e | |||
bf703a8a66 | |||
e7b631b087 | |||
a9f5dc036c | |||
0a83b51e00 | |||
eab63ecc6c | |||
b0794cf35e | |||
5b9e71a27d | |||
eae41de27d | |||
e9163aa3a7 | |||
8c617515ba | |||
04e4e71f2e | |||
a587482edf | |||
0aac9350d5 | |||
f56c12ee4e | |||
4bb9ae61f2 | |||
ff7f3484e4 | |||
5da3abe6b4 | |||
c0b398e0ce | |||
3de10adac2 | |||
1b573d6552 | |||
2a96f93919 | |||
c6b2639ca4 | |||
b9abf37a7e | |||
373cbb4144 | |||
a521982576 | |||
a77fde577c | |||
ea6926e57d | |||
ba25b7fee6 | |||
7ee162d98b | |||
380f557c45 | |||
1bdae53f4e | |||
9314c346da | |||
bfaad1388c | |||
0b580ad05d | |||
bb35a80177 | |||
24fc95ac81 | |||
8f864417c4 | |||
bb9d29b061 | |||
b9d8ec1463 | |||
1842a7660d | |||
5caa2f5536 | |||
d6078be8b7 | |||
cf60723f14 | |||
f7ff0a2b1d | |||
cc49664b2f | |||
99fe74f026 | |||
b021869eeb | |||
b8806d163b | |||
1116aae1de | |||
5e5f60253b | |||
bbc02752c9 | |||
9896bc110e | |||
ca60f8ecdd | |||
544acd1e35 | |||
6e07602d77 | |||
82898f7bba | |||
d61283a8bc | |||
1ee3f826cc | |||
4a00a5ba9e | |||
39eda67867 | |||
a99d38fdaa | |||
0eb2d437e2 | |||
3ac9036c79 | |||
c94e292176 | |||
91d87c2d9b | |||
ff472f69c0 | |||
e18119e24c | |||
4a592dc64c | |||
d9e13201dd | |||
5c75b19c5d | |||
52a77db60f | |||
0513c250fb | |||
48864ad6cf | |||
cdbccad21e | |||
e15bc68c9b | |||
8bffd7672d | |||
61df5b3060 | |||
b5255444cd | |||
0c94e377fc | |||
8e5c67b4b2 | |||
b24f2f1756 | |||
c69c17de42 | |||
061617122a | |||
125ce3240f | |||
7215efe167 | |||
06d1570142 | |||
093c370faa | |||
aec9574737 | |||
7ceb76cff5 | |||
300e2fe9f8 | |||
91e1643627 | |||
91421b0c62 | |||
40f611664f | |||
dcba4f4098 | |||
c098ad2b3b | |||
b43223cb7a | |||
e243531dab | |||
1af38e62bc | |||
f37f062cdc | |||
7e734214dc | |||
05d152746f | |||
dea7f37553 | |||
415c599310 | |||
70cd4fedbe | |||
1e6d7673bc | |||
b4963b725b | |||
0371ffa4ce | |||
6a664a7e15 | |||
88ce9300bc | |||
85cf0e311c | |||
0e3d75cfeb | |||
630c8a5faa | |||
905921a684 | |||
1e469b3b0f | |||
bff3c4f95c | |||
bd2bcb6994 | |||
4c8898a639 | |||
97df33ab1a | |||
ef46fb2685 | |||
d5d6dd3614 | |||
6c233c6a0a | |||
6db715d879 | |||
ab02e8a546 | |||
8cbfe64f19 | |||
fd1e9971e4 | |||
68336a76c5 | |||
393e914a86 | |||
ffb54110e9 | |||
533d825f1a | |||
c65279b672 | |||
f9926beeef | |||
add8a777d8 | |||
21bc505d85 | |||
3fc49c001e | |||
3d69a95c49 | |||
d81fdf6d6b | |||
87d3109ffb | |||
180dbbb521 | |||
24aac7cee5 | |||
53e18a5387 | |||
92062d056d | |||
06368ab0a1 | |||
38efe25c68 | |||
319079de7a | |||
025bf900a5 | |||
2885f4f7b1 | |||
c07eda15b1 | |||
4274296cf3 | |||
76a203d4df | |||
24f37e2062 | |||
f465b2e2a0 | |||
ce00e49a89 | |||
d494f9d66b | |||
c35a183a64 | |||
9cdd5fe7f2 | |||
c21428215e | |||
64d5af46f5 | |||
25846ea18a | |||
798383596d | |||
9ca71bc937 | |||
5407429ec0 | |||
ee5c94f6db | |||
91045afbee | |||
3f64782023 | |||
f8d35f9502 | |||
ea78d3ec9a | |||
e056a28316 | |||
0bea721c2e | |||
e1b89494d0 | |||
cd8e7f3912 | |||
50604c25c2 | |||
aa6b2357d8 | |||
5b2d29bef6 | |||
a296d26328 | |||
d01a26ec61 | |||
efd7d6f0c0 | |||
b55be093be | |||
7c1d5cadd7 | |||
dd1592b03b | |||
9b37ac483f | |||
090820958e | |||
ac21e1be5c | |||
5196443b26 | |||
c8531cbeb1 | |||
c560abedba | |||
9b952fbc44 | |||
ccdf05e922 | |||
c3d74f2ae9 | |||
f47498888c | |||
5665a7f0cb | |||
b8178c6c8d | |||
4a0f15eb88 | |||
c4f53fe525 | |||
8c93ec52de | |||
befe0fff2a | |||
b6a837cbea | |||
4861973899 | |||
c593e4b500 | |||
5bf78c20d4 | |||
5c672130e6 | |||
d8214d4f12 | |||
64d1f09ce0 | |||
47d0f0ea40 | |||
2d85fd093e | |||
d936568b76 | |||
4598a83e8e | |||
f4bf00ad31 | |||
07fde7f6cc | |||
729209574e | |||
f28206d989 | |||
0c81b32cac | |||
11216017cb | |||
a7b9f53967 | |||
1fa2e2e37d | |||
f67d5faeb7 | |||
5cbf859458 | |||
629ed74d09 | |||
ca2af2ca63 | |||
52ab089615 | |||
01461a196d | |||
04832f052a | |||
c8b2c8ae50 | |||
1b81c7fb22 | |||
9ccda0247e | |||
a7df4dcf25 | |||
d91f47c791 | |||
a9ac4e7f44 | |||
fc3ec57437 | |||
266f6ab919 | |||
6218c1c00b | |||
cc81d6fe82 | |||
69f9102f2d | |||
beb9275982 | |||
abe48713f2 | |||
82cfaf2fbb | |||
3d3bc4738f | |||
2d0746f5a4 | |||
9c71e2f1c8 | |||
134fd62da8 | |||
2afd283582 | |||
c66734bab0 | |||
8e56a61f95 | |||
d265271148 | |||
b40e397b28 | |||
35ff1d996a | |||
deea0b05cb | |||
c50c9ca545 | |||
a819b4a5a5 | |||
df2d7d4734 | |||
79ce4098cf | |||
374464a1f8 | |||
c8d0bf27af | |||
6e4ae034b2 | |||
52b560e72d | |||
9b971ad222 | |||
3613162d09 | |||
3a272e998d | |||
d4c750beb4 | |||
84b31e65e1 | |||
7b802bfd3d | |||
f9c4632b8d | |||
e4764cd8a6 | |||
dd78a3a686 | |||
94c06e13f4 | |||
e8bebe5a75 | |||
5b0e1b4f9e | |||
8c0a93779b | |||
9241479da4 | |||
8ffca93cd5 | |||
7fea0c124a | |||
20dbdb20d2 | |||
e6b8e2e8be | |||
7c5b7f77cc | |||
de84547a21 | |||
44676756ae | |||
b399b0f182 | |||
1152191f48 | |||
af1b07ad44 | |||
b8113fff1e | |||
ff6948cf2d | |||
fd25e85d59 | |||
c07cd72e85 | |||
e2c101206c | |||
92276b5769 | |||
a2133f61a8 | |||
199adbbcf0 | |||
dc316fd7b4 | |||
025183602f | |||
db4619a9d9 | |||
451e527b7c | |||
54dd3a00df | |||
03c5dab79d | |||
1fdee861e8 | |||
c12bf991b3 | |||
78a097585d | |||
39132327cc | |||
dc32318cec | |||
592f74124c | |||
e5e63cc5ac | |||
f40e0f786d | |||
ebd9f1471b | |||
d76547ead4 | |||
4600772e05 | |||
ed597423cd | |||
f20ca06f85 | |||
a636d3f394 | |||
043df18daa | |||
96996bf18e | |||
f350137a14 | |||
b7a6f3ec75 | |||
6c34672549 | |||
e779a07bce | |||
9a36e8ba3b | |||
c968bacb01 | |||
25199dfb43 | |||
48fed4e6fb | |||
fc253237c9 | |||
589948c7f4 | |||
7e69690605 | |||
95f498ba9b | |||
fd07ae5225 | |||
8acd94fc89 | |||
1436480eab | |||
448d176c24 | |||
fd269453a4 | |||
b3b380964c | |||
6e9025ebf2 | |||
3922691b3c | |||
0545b77cf4 | |||
6b3f39fa1a | |||
3114ab87dc | |||
00bc99cc7b | |||
540b3ae2f4 | |||
dbfe4140e1 | |||
d3675ec254 | |||
ded2483fc0 | |||
e62ea388e0 | |||
f20356e9be | |||
d282a2d846 | |||
4641ac46e7 | |||
ba9268a09e | |||
fb9902c536 | |||
5318ba6c6e | |||
fd5ebef488 | |||
d9e4f39ddc | |||
435b9d8973 | |||
0ea70ba656 | |||
92a07b87d2 | |||
c3c82282ba | |||
adc15c24ef | |||
dddf9a9396 | |||
9ca6860ffa | |||
f7dd388954 | |||
6012839f0e | |||
8e9cbab053 | |||
aaf375a57b | |||
3cce985f03 | |||
c59df6ec20 | |||
5c3f41f64d | |||
cf3523f49f | |||
db794752cb | |||
bceaebe856 | |||
3916de2921 | |||
9e0f8c1a97 | |||
0cbc56b82e | |||
b95608f68a | |||
b6e5dbd06c | |||
914f19be86 | |||
f09bcf3fcf | |||
d0b18dec8e | |||
75d486b124 | |||
4914609485 | |||
75bd66326a | |||
8f904f75bb | |||
549c598f51 | |||
ed68d604d6 | |||
f83752f43b | |||
86c22636eb | |||
30d20a453b | |||
fe29d8a23f | |||
694d088160 | |||
6aabbffc62 | |||
7b59bc8d12 | |||
79d0fb0b52 | |||
edf56d34f8 | |||
623329fb33 | |||
9f0074eef9 | |||
6733253826 | |||
f117805129 | |||
c75b1581d2 | |||
109e118aba | |||
201b77d5b6 | |||
a5ca08f33d | |||
86210c4513 | |||
988a3e4446 | |||
0f5cd22bb7 | |||
2f5bed36b3 | |||
5b6534bb28 | |||
e31e5b2477 | |||
07d5fafe2e | |||
e08da659e5 | |||
8a4979f44c | |||
e67464325f | |||
94c9b0d23b | |||
e9ec310d8a | |||
c78d1e3c39 | |||
e94319145f | |||
3f3b01b5f6 | |||
19a2791c65 | |||
4e8ccf0ef3 | |||
f1a7d5ecf7 | |||
8b05abb80d | |||
48c9349ce9 | |||
117d848466 | |||
9a2df072cc | |||
99c62aab36 | |||
224278e07a | |||
74b69e191e | |||
8cda8a727c | |||
a3c0c7c96f | |||
4403e4ed62 | |||
9b209823f6 | |||
b2cb125bd4 | |||
5e8f767642 | |||
6ee270d9d8 | |||
44fa309d20 | |||
58d88f3dd4 | |||
e980c23177 | |||
75224321bb | |||
801af05b20 | |||
7611dbbddc | |||
6d40ca15bc | |||
32c1c19224 | |||
bbf6357222 | |||
dc16629c24 | |||
3718b9d768 | |||
c25eb088ec | |||
3feb3e52f8 | |||
8e730ef93d | |||
e0913a39ab | |||
7a27fbc001 | |||
ee0dbdad35 | |||
9225f88f89 | |||
a04839dd6b | |||
002006517a | |||
f5b202d438 | |||
a7df094ff4 | |||
1e6fa77633 | |||
eb4cff202c | |||
7ee777f405 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
#patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: irmen
|
||||||
|
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
#liberapay: # Replace with a single Liberapay username
|
||||||
|
#issuehunt: # Replace with a single IssueHunt username
|
||||||
|
#otechie: # Replace with a single Otechie username
|
||||||
|
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
36
.github/workflows/all-ci.yml
vendored
Normal file
36
.github/workflows/all-ci.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Build and Test the Prog8 compiler
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: build and install recent 64tass
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y make build-essential
|
||||||
|
git clone --depth=1 https://github.com/irmen/64tass
|
||||||
|
cd 64tass
|
||||||
|
make -j4
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Build and test with Gradle
|
||||||
|
run: ./gradlew build shadowJar --no-daemon
|
||||||
|
|
||||||
|
- name: Create compiler shadowJar artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: prog8-compiler-jar-zipped
|
||||||
|
path: compiler/build/libs/*-all.jar
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,6 +15,7 @@ out/
|
|||||||
parser/**/*.interp
|
parser/**/*.interp
|
||||||
parser/**/*.tokens
|
parser/**/*.tokens
|
||||||
parser/**/*.java
|
parser/**/*.java
|
||||||
|
compiler/src/prog8/buildversion/*
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*.egg
|
*.egg
|
||||||
*.egg-info
|
*.egg-info
|
||||||
@ -29,6 +30,8 @@ parsetab.py
|
|||||||
compiler/lib/
|
compiler/lib/
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
|
**/BuildVersion.kt
|
||||||
/prog8compiler.jar
|
/prog8compiler.jar
|
||||||
sd*.img
|
sd*.img
|
||||||
*.d64
|
*.d64
|
||||||
|
|
||||||
|
9
.idea/kotlinc.xml
generated
Normal file
9
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
|
<option name="jvmTarget" value="11" />
|
||||||
|
</component>
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.9.20" />
|
||||||
|
</component>
|
||||||
|
</project>
|
28
.idea/libraries/KotlinJavaRuntime.xml
generated
28
.idea/libraries/KotlinJavaRuntime.xml
generated
@ -1,19 +1,23 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="KotlinJavaRuntime">
|
<library name="KotlinJavaRuntime" type="repository">
|
||||||
|
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.22" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
|
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-javadoc.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-javadoc.jar!/" />
|
||||||
|
</JAVADOC>
|
||||||
<SOURCES>
|
<SOURCES>
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-sources.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-sources.jar!/" />
|
||||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
|
|
||||||
</SOURCES>
|
</SOURCES>
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
7
.idea/libraries/antlr_antlr4.xml
generated
7
.idea/libraries/antlr_antlr4.xml
generated
@ -1,17 +1,16 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="antlr.antlr4" type="repository">
|
<library name="antlr.antlr4" type="repository">
|
||||||
<properties maven-id="org.antlr:antlr4:4.11.1">
|
<properties maven-id="org.antlr:antlr4:4.13.1">
|
||||||
<exclude>
|
<exclude>
|
||||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||||
</exclude>
|
</exclude>
|
||||||
</properties>
|
</properties>
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.11.1/antlr4-4.11.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.11.1/antlr4-runtime-4.11.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.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/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/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!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
30
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
30
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
@ -1,23 +1,23 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
|
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.2" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
30
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
30
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
@ -1,21 +1,21 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.3.2" />
|
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.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$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
24
.idea/libraries/io_kotest_property_jvm.xml
generated
24
.idea/libraries/io_kotest_property_jvm.xml
generated
@ -1,24 +0,0 @@
|
|||||||
<component name="libraryTable">
|
|
||||||
<library name="io.kotest.property.jvm" type="repository">
|
|
||||||
<properties maven-id="io.kotest:kotest-property-jvm:5.3.2" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.3.2/kotest-property-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.3/rgxgen-1.3.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.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/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</component>
|
|
73
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
73
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
@ -1,51 +1,42 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.3.2" />
|
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.3.2/kotest-runner-junit5-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.3.2/kotest-framework-api-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.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.6.1/kotlinx-coroutines-test-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.3.2/kotest-framework-engine-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.146/classgraph-4.8.146.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.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/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$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-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.6.1/kotlinx-coroutines-debug-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.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/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/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.3.2/kotest-framework-discovery-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.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$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.3.2/kotest-extensions-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.12.3/mockk-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.12.3/mockk-common-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.12.3/mockk-dsl-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.12.3/mockk-dsl-jvm-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.12.3/mockk-agent-jvm-1.12.3.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$/io/mockk/mockk-agent-api/1.12.3/mockk-agent-api-1.12.3.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$/io/mockk/mockk-agent-common/1.12.3/mockk-agent-common-1.12.3.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/objenesis/objenesis/3.1/objenesis-3.1.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$/net/bytebuddy/byte-buddy/1.12.6/byte-buddy-1.12.6.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$/net/bytebuddy/byte-buddy-agent/1.12.6/byte-buddy-agent-1.12.6.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$/io/kotest/kotest-framework-concurrency-jvm/5.3.2/kotest-framework-concurrency-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.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/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
4
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
4
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
@ -1,8 +1,8 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.4" />
|
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.4/kotlinx-cli-jvm-0.3.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
12
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
12
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
@ -1,13 +1,13 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" />
|
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.16/kotlin-result-jvm-1.1.16.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.18/kotlin-result-jvm-1.1.18.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.20/kotlin-stdlib-common-1.6.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.20/kotlin-stdlib-jdk8-1.6.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.20/kotlin-stdlib-1.6.20.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.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/1.6.20/kotlin-stdlib-jdk7-1.6.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
6
.idea/libraries/slf4j_simple.xml
generated
6
.idea/libraries/slf4j_simple.xml
generated
@ -1,9 +1,9 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="slf4j.simple" type="repository">
|
<library name="slf4j.simple" type="repository">
|
||||||
<properties maven-id="org.slf4j:slf4j-simple:1.7.36" />
|
<properties maven-id="org.slf4j:slf4j-simple:2.0.11" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.36/slf4j-simple-1.7.36.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.11/slf4j-simple-2.0.11.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.11/slf4j-api-2.0.11.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
19
.idea/libraries/takes.xml
generated
19
.idea/libraries/takes.xml
generated
@ -1,15 +1,16 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="takes" type="repository">
|
<library name="takes" type="repository">
|
||||||
<properties maven-id="org.takes:takes:1.20" />
|
<properties maven-id="org.takes:takes:1.24.4" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.20/takes-1.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.24.4/takes-1.24.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.50/cactoos-0.50.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.54.0/cactoos-0.54.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.4.0-b180830.0359/jaxb-api-2.4.0-b180830.0359.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/4.0.0/jaxb-core-4.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/2.3.0/jaxb-core-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/2.3.0/jaxb-impl-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/1.0.0/angus-activation-1.0.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/4.0.0/jaxb-impl-4.0.0.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@ -16,10 +16,13 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.11" />
|
||||||
|
</component>
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<type id="Python" />
|
<type id="Python" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -7,13 +7,9 @@ version: 2
|
|||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
# Set the version of Python and other tools you might need
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: "3.11"
|
||||||
# You can also specify other tool versions:
|
|
||||||
# nodejs: "16"
|
|
||||||
# rust: "1.55"
|
|
||||||
# golang: "1.17"
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
sphinx:
|
sphinx:
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
#### Just a few remarks upfront:
|
|
||||||
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
|
|
||||||
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
|
|
||||||
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
|
|
||||||
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
|
|
||||||
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
|
|
||||||
|
|
||||||
#### Problems with `compilerAst`:
|
|
||||||
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
|
|
||||||
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
|
|
||||||
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
|
|
||||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
|
||||||
|
|
||||||
#### Steps to take, in conceptual (!) order:
|
|
||||||
|
|
||||||
(note: all these steps have been implemented, rejected or otherwise solved now.)
|
|
||||||
|
|
||||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
|
||||||
- from the local file system (use case: user programs)
|
|
||||||
- from resources (prog8lib)
|
|
||||||
- from plain strings (for testing)
|
|
||||||
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
|
|
||||||
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
|
|
||||||
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
|
|
||||||
- plus, optionally, method's for registering/unregistering a listener with the parser
|
|
||||||
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
|
|
||||||
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
|
|
||||||
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
|
|
||||||
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
|
|
||||||
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
|
|
||||||
6. move `IStringEncoding` to module `compiler`
|
|
||||||
7. same with `ModuleImporter`, then rewrite that (addressing #46)
|
|
||||||
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
|
|
||||||
9. re-think `IStringEncoding` to address #38
|
|
||||||
|
|
29
README.md
29
README.md
@ -1,3 +1,4 @@
|
|||||||
|
[](https://ko-fi.com/H2H6S0FFF)
|
||||||
[](https://prog8.readthedocs.io/)
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||||
@ -9,11 +10,33 @@ This is a structured programming language for the 8-bit 6502/6510/65c02 micropro
|
|||||||
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 is a medium to low level programming language,
|
||||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||||
|
|
||||||
|
**Want to buy me a coffee or a pizza perhaps?**
|
||||||
|
|
||||||
|
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
|
||||||
|
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).
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||||
https://prog8.readthedocs.io/
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
|
How to get it/build it
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- Download the latest [official release](https://github.com/irmen/prog8/releases) from github.
|
||||||
|
- Or, if you want/need a bleeding edge development version, you can:
|
||||||
|
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
|
||||||
|
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
Most of the development on Prog8 and the use of it is currently centered around
|
||||||
|
the [Commander X16](https://www.commanderx16.com/) retro computer. Their [discord server](https://discord.gg/nS2PqEC) contains a small channel
|
||||||
|
dedicated to Prog8. Other than that, use the issue tracker on github.
|
||||||
|
|
||||||
|
|
||||||
Software license
|
Software license
|
||||||
----------------
|
----------------
|
||||||
GNU GPL 3.0 (see file LICENSE), with exception for generated code:
|
GNU GPL 3.0 (see file LICENSE), with exception for generated code:
|
||||||
@ -59,6 +82,8 @@ What does Prog8 provide?
|
|||||||
- "c64": Commodore-64 (6502 like CPU)
|
- "c64": Commodore-64 (6502 like CPU)
|
||||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||||
|
- "pet32": Commodore PET (experimental)
|
||||||
|
- "atari": Atari 8 bit such as 800XL (experimental)
|
||||||
- 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)
|
- 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)
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +101,9 @@ IntelliJ IDEA with the Kotlin plugin).
|
|||||||
|
|
||||||
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
and a recent emulator version (R42 or newer) for the CommanderX16, such as [x16emu](https://cx16forum.com/forum/viewforum.php?f=30)
|
||||||
|
(preferred, this is the official emulator. If required, source code is [here](https://github.com/X16Community/x16-emulator/)).
|
||||||
|
There is also [Box16](https://github.com/indigodarkwolf/box16) which has powerful debugging features.
|
||||||
|
|
||||||
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
||||||
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
||||||
|
@ -26,16 +26,16 @@ compileTestKotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// should have no dependencies to other modules
|
// should have no dependencies to other modules
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDir "${project.projectDir}/src"
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDir "${project.projectDir}/res"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package prog8.code
|
package prog8.code
|
||||||
|
|
||||||
|
import prog8.code.ast.PtNode
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
@ -7,19 +9,32 @@ import prog8.code.core.*
|
|||||||
* Tree structure containing all symbol definitions in the program
|
* Tree structure containing all symbol definitions in the program
|
||||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||||
*/
|
*/
|
||||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
|
||||||
/**
|
/**
|
||||||
* The table as a flat mapping of scoped names to the StNode.
|
* The table as a flat mapping of scoped names to the StNode.
|
||||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
||||||
*/
|
*/
|
||||||
val flat: Map<List<String>, StNode> by lazy {
|
|
||||||
val result = mutableMapOf<List<String>, StNode>()
|
private var cachedFlat: Map<String, StNode>? = null
|
||||||
fun flatten(node: StNode) {
|
|
||||||
result[node.scopedName] = node
|
val flat: Map<String, StNode> get() {
|
||||||
node.children.values.forEach { flatten(it) }
|
if(cachedFlat!=null)
|
||||||
|
return cachedFlat!!
|
||||||
|
|
||||||
|
val result = mutableMapOf<String, StNode>()
|
||||||
|
fun collect(node: StNode) {
|
||||||
|
for(child in node.children) {
|
||||||
|
result[child.value.scopedName] = child.value
|
||||||
|
collect(child.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
children.values.forEach { flatten(it) }
|
collect(this)
|
||||||
result
|
cachedFlat = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetCachedFlat() {
|
||||||
|
cachedFlat = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val allVariables: Collection<StStaticVariable> by lazy {
|
val allVariables: Collection<StStaticVariable> by lazy {
|
||||||
@ -51,10 +66,30 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
||||||
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
val vars = mutableListOf<StMemorySlab>()
|
||||||
|
fun collect(node: StNode) {
|
||||||
|
for(child in node.children) {
|
||||||
|
if(child.value.type== StNodeType.MEMORYSLAB)
|
||||||
|
vars.add(child.value as StMemorySlab)
|
||||||
|
else
|
||||||
|
collect(child.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collect(this)
|
||||||
|
vars
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
override fun lookup(scopedName: String) = flat[scopedName]
|
||||||
|
|
||||||
|
fun getLength(name: String): Int? {
|
||||||
|
val node = flat[name]
|
||||||
|
return when(node) {
|
||||||
|
is StMemVar -> node.length
|
||||||
|
is StMemorySlab -> node.size.toInt()
|
||||||
|
is StStaticVariable -> node.length
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,44 +110,24 @@ enum class StNodeType {
|
|||||||
|
|
||||||
open class StNode(val name: String,
|
open class StNode(val name: String,
|
||||||
val type: StNodeType,
|
val type: StNodeType,
|
||||||
val position: Position,
|
val astNode: PtNode,
|
||||||
val children: MutableMap<String, StNode> = mutableMapOf()
|
val children: MutableMap<String, StNode> = mutableMapOf()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
lateinit var parent: StNode
|
lateinit var parent: StNode
|
||||||
|
|
||||||
val scopedName: List<String> by lazy {
|
val scopedName: String by lazy { scopedNameList.joinToString(".") }
|
||||||
if(type== StNodeType.GLOBAL)
|
|
||||||
emptyList()
|
|
||||||
else
|
|
||||||
parent.scopedName + name
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookup(name: String) =
|
open fun lookup(scopedName: String) =
|
||||||
lookupUnqualified(name)
|
lookup(scopedName.split('.'))
|
||||||
open fun lookup(scopedName: List<String>) =
|
|
||||||
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
|
|
||||||
fun lookupOrElse(name: String, default: () -> StNode) =
|
|
||||||
lookupUnqualified(name) ?: default()
|
|
||||||
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
|
|
||||||
lookup(scopedName) ?: default()
|
|
||||||
|
|
||||||
private fun lookupQualified(scopedName: List<String>): StNode? {
|
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
|
||||||
// a scoped name refers to a name in another namespace, and always stars from the root.
|
lookupUnscoped(name) ?: default()
|
||||||
var node = this
|
|
||||||
while(node.type!= StNodeType.GLOBAL)
|
|
||||||
node = node.parent
|
|
||||||
|
|
||||||
for(name in scopedName) {
|
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
|
||||||
if(name in node.children)
|
lookup(scopedName.split('.')) ?: default()
|
||||||
node = node.children.getValue(name)
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lookupUnqualified(name: String): StNode? {
|
fun lookupUnscoped(name: String): StNode? {
|
||||||
// first consider the builtin functions
|
// first consider the builtin functions
|
||||||
var globalscope = this
|
var globalscope = this
|
||||||
while(globalscope.type!= StNodeType.GLOBAL)
|
while(globalscope.type!= StNodeType.GLOBAL)
|
||||||
@ -138,37 +153,54 @@ open class StNode(val name: String,
|
|||||||
children[child.name] = child
|
children[child.name] = child
|
||||||
child.parent = this
|
child.parent = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scopedNameList: List<String> by lazy {
|
||||||
|
if(type==StNodeType.GLOBAL)
|
||||||
|
emptyList()
|
||||||
|
else
|
||||||
|
parent.scopedNameList + name
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lookup(scopedName: List<String>): StNode? {
|
||||||
|
// a scoped name refers to a name in another namespace, and always stars from the root.
|
||||||
|
var node = this
|
||||||
|
while(node.type!=StNodeType.GLOBAL)
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
for(name in scopedName) {
|
||||||
|
if(name in node.children)
|
||||||
|
node = node.children.getValue(name)
|
||||||
|
else
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StStaticVariable(name: String,
|
class StStaticVariable(name: String,
|
||||||
val dt: DataType,
|
val dt: DataType,
|
||||||
val bss: Boolean,
|
|
||||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||||
val onetimeInitializationStringValue: StString?,
|
val onetimeInitializationStringValue: StString?,
|
||||||
val onetimeInitializationArrayValue: StArray?,
|
val onetimeInitializationArrayValue: StArray?,
|
||||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||||
val zpwish: ZeropageWish,
|
val zpwish: ZeropageWish, // used in the variable allocator
|
||||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||||
|
|
||||||
|
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(bss) {
|
|
||||||
require(onetimeInitializationNumericValue==null)
|
|
||||||
require(onetimeInitializationStringValue==null)
|
|
||||||
require(onetimeInitializationArrayValue.isNullOrEmpty())
|
|
||||||
} else {
|
|
||||||
require(onetimeInitializationNumericValue!=null ||
|
|
||||||
onetimeInitializationStringValue!=null ||
|
|
||||||
onetimeInitializationArrayValue!=null)
|
|
||||||
}
|
|
||||||
if(length!=null) {
|
if(length!=null) {
|
||||||
require(onetimeInitializationNumericValue == null)
|
require(onetimeInitializationNumericValue == null)
|
||||||
if(onetimeInitializationArrayValue!=null)
|
if(onetimeInitializationArrayValue!=null)
|
||||||
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||||
}
|
}
|
||||||
if(onetimeInitializationNumericValue!=null)
|
if(onetimeInitializationNumericValue!=null) {
|
||||||
require(dt in NumericDatatypes)
|
require(dt in NumericDatatypes)
|
||||||
if(onetimeInitializationArrayValue!=null)
|
}
|
||||||
|
if(onetimeInitializationArrayValue!=null) {
|
||||||
require(dt in ArrayDatatypes)
|
require(dt in ArrayDatatypes)
|
||||||
|
require(length==onetimeInitializationArrayValue.size)
|
||||||
|
}
|
||||||
if(onetimeInitializationStringValue!=null) {
|
if(onetimeInitializationStringValue!=null) {
|
||||||
require(dt == DataType.STR)
|
require(dt == DataType.STR)
|
||||||
require(length == onetimeInitializationStringValue.first.length+1)
|
require(length == onetimeInitializationStringValue.first.length+1)
|
||||||
@ -177,45 +209,48 @@ class StStaticVariable(name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
|
||||||
StNode(name, StNodeType.CONSTANT, position) {
|
StNode(name, StNodeType.CONSTANT, astNode)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class StMemVar(name: String,
|
class StMemVar(name: String,
|
||||||
val dt: DataType,
|
val dt: DataType,
|
||||||
val address: UInt,
|
val address: UInt,
|
||||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||||
position: Position) :
|
astNode: PtNode) :
|
||||||
StNode(name, StNodeType.MEMVAR, position) {
|
StNode(name, StNodeType.MEMVAR, astNode) {
|
||||||
|
|
||||||
|
init{
|
||||||
|
if(dt in ArrayDatatypes || dt == DataType.STR)
|
||||||
|
require(length!=null) { "memory mapped array or string must have known length" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StMemorySlab(
|
class StMemorySlab(
|
||||||
name: String,
|
name: String,
|
||||||
val size: UInt,
|
val size: UInt,
|
||||||
val align: UInt,
|
val align: UInt,
|
||||||
position: Position
|
astNode: PtNode
|
||||||
):
|
):
|
||||||
StNode(name, StNodeType.MEMORYSLAB, position) {
|
StNode(name, StNodeType.MEMORYSLAB, astNode)
|
||||||
}
|
|
||||||
|
|
||||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
|
||||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
|
||||||
}
|
StNode(name, StNodeType.SUBROUTINE, astNode)
|
||||||
|
|
||||||
|
|
||||||
class StRomSub(name: String,
|
class StRomSub(name: String,
|
||||||
val address: UInt,
|
val address: UInt?, // null in case of asmsub, specified in case of romsub
|
||||||
val parameters: List<StRomSubParameter>,
|
val parameters: List<StRomSubParameter>,
|
||||||
val returns: List<RegisterOrStatusflag>,
|
val returns: List<StRomSubParameter>,
|
||||||
position: Position) :
|
astNode: PtNode) :
|
||||||
StNode(name, StNodeType.ROMSUB, position) {
|
StNode(name, StNodeType.ROMSUB, astNode)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class StSubroutineParameter(val name: String, val type: DataType)
|
class StSubroutineParameter(val name: String, val type: DataType)
|
||||||
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||||
class StArrayElement(val number: Double?, val addressOf: List<String>?)
|
class StArrayElement(val number: Double?, val addressOfSymbol: String?)
|
||||||
|
|
||||||
typealias StString = Pair<String, Encoding>
|
typealias StString = Pair<String, Encoding>
|
||||||
typealias StArray = List<StArrayElement>
|
typealias StArray = List<StArrayElement>
|
||||||
|
206
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
206
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package prog8.code
|
||||||
|
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||||
|
fun make(): SymbolTable {
|
||||||
|
val st = SymbolTable(program)
|
||||||
|
|
||||||
|
BuiltinFunctions.forEach {
|
||||||
|
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
|
||||||
|
}
|
||||||
|
|
||||||
|
val scopestack = Stack<StNode>()
|
||||||
|
scopestack.push(st)
|
||||||
|
program.children.forEach {
|
||||||
|
addToSt(it, scopestack)
|
||||||
|
}
|
||||||
|
require(scopestack.size==1)
|
||||||
|
|
||||||
|
if(options.compTarget.name != VMTarget.NAME) {
|
||||||
|
listOf(
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
|
||||||
|
).forEach {
|
||||||
|
it.parent = program
|
||||||
|
st.add(StMemVar(it.name, it.type, it.address, it.arraySize?.toInt(), it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
|
||||||
|
val stNode = when(node) {
|
||||||
|
is PtAsmSub -> {
|
||||||
|
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||||
|
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
|
||||||
|
StRomSub(node.name, node.address, parameters, returns, node)
|
||||||
|
}
|
||||||
|
is PtBlock -> {
|
||||||
|
StNode(node.name, StNodeType.BLOCK, node)
|
||||||
|
}
|
||||||
|
is PtConstant -> {
|
||||||
|
StConstant(node.name, node.type, node.value, node)
|
||||||
|
}
|
||||||
|
is PtLabel -> {
|
||||||
|
StNode(node.name, StNodeType.LABEL, node)
|
||||||
|
}
|
||||||
|
is PtMemMapped -> {
|
||||||
|
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
|
||||||
|
StSub(node.name, params, node.returntype, node)
|
||||||
|
}
|
||||||
|
is PtVariable -> {
|
||||||
|
val initialNumeric: Double?
|
||||||
|
val initialString: StString?
|
||||||
|
val initialArray: StArray?
|
||||||
|
val numElements: Int?
|
||||||
|
val value = node.value
|
||||||
|
if(value!=null) {
|
||||||
|
val number = (value as? PtNumber)?.number
|
||||||
|
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
|
||||||
|
when (value) {
|
||||||
|
is PtString -> {
|
||||||
|
initialString = StString(value.value, value.encoding)
|
||||||
|
initialArray = null
|
||||||
|
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||||
|
}
|
||||||
|
is PtArray -> {
|
||||||
|
initialArray = makeInitialArray(value)
|
||||||
|
initialString = null
|
||||||
|
numElements = initialArray.size
|
||||||
|
require(node.arraySize?.toInt()==numElements)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
initialString = null
|
||||||
|
initialArray = null
|
||||||
|
numElements = node.arraySize?.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
initialNumeric = null
|
||||||
|
initialArray = null
|
||||||
|
initialString = null
|
||||||
|
numElements = node.arraySize?.toInt()
|
||||||
|
}
|
||||||
|
// if(node.type in SplitWordArrayTypes) {
|
||||||
|
// ... split array also add _lsb and _msb to symboltable?
|
||||||
|
// }
|
||||||
|
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||||
|
}
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
if(node.name=="memory") {
|
||||||
|
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||||
|
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||||
|
val slabname = (node.args[0] as PtString).value
|
||||||
|
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||||
|
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||||
|
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||||
|
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else -> null // node is not present in the ST
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stNode!=null) {
|
||||||
|
scope.peek().add(stNode)
|
||||||
|
scope.push(stNode)
|
||||||
|
}
|
||||||
|
node.children.forEach {
|
||||||
|
addToSt(it, scope)
|
||||||
|
}
|
||||||
|
if(stNode!=null)
|
||||||
|
scope.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||||
|
return value.children.map {
|
||||||
|
when(it) {
|
||||||
|
is PtAddressOf -> {
|
||||||
|
if(it.isFromArrayElement)
|
||||||
|
TODO("address-of array element $it in initial array value")
|
||||||
|
StArrayElement(null, it.identifier.name)
|
||||||
|
}
|
||||||
|
is PtIdentifier -> StArrayElement(null, it.name)
|
||||||
|
is PtNumber -> StArrayElement(it.number, null)
|
||||||
|
else -> throw AssemblyError("invalid array element $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// override fun visit(decl: VarDecl) {
|
||||||
|
// val node =
|
||||||
|
// when(decl.type) {
|
||||||
|
// VarDeclType.VAR -> {
|
||||||
|
// var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||||
|
// if(initialNumeric==0.0)
|
||||||
|
// initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||||
|
// val initialStringLit = decl.value as? StringLiteral
|
||||||
|
// val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||||
|
// val initialArrayLit = decl.value as? ArrayLiteral
|
||||||
|
// val initialArray = makeInitialArray(initialArrayLit)
|
||||||
|
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
|
||||||
|
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
|
||||||
|
// val numElements =
|
||||||
|
// if(decl.isArray)
|
||||||
|
// decl.arraysize!!.constIndex()
|
||||||
|
// else if(initialStringLit!=null)
|
||||||
|
// initialStringLit.value.length+1 // include the terminating 0-byte
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// val bss = if(decl.datatype==DataType.STR)
|
||||||
|
// false
|
||||||
|
// else if(decl.isArray)
|
||||||
|
// initialArray.isNullOrEmpty()
|
||||||
|
// else
|
||||||
|
// initialNumeric == null
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// VarDeclType.CONST -> {
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// VarDeclType.MEMORY -> {
|
||||||
|
// val numElements =
|
||||||
|
// if(decl.isArray)
|
||||||
|
// decl.arraysize!!.constIndex()
|
||||||
|
// else null
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// scopestack.peek().add(node)
|
||||||
|
// // st.origAstLinks[decl] = node
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
|
||||||
|
// if(arrayLit==null)
|
||||||
|
// return null
|
||||||
|
// return arrayLit.value.map {
|
||||||
|
// when(it){
|
||||||
|
// is AddressOf -> {
|
||||||
|
// val scopedName = it.identifier.targetNameAndType(program).first
|
||||||
|
// StArrayElement(null, scopedName)
|
||||||
|
// }
|
||||||
|
// is IdentifierReference -> {
|
||||||
|
// val scopedName = it.targetNameAndType(program).first
|
||||||
|
// StArrayElement(null, scopedName)
|
||||||
|
// }
|
||||||
|
// is NumericLiteral -> StArrayElement(it.number, null)
|
||||||
|
// else -> throw FatalAstException("weird element dt in array literal")
|
||||||
|
// }
|
||||||
|
// }.toList()
|
||||||
|
// }
|
||||||
|
//
|
@ -1,6 +1,9 @@
|
|||||||
package prog8.code.ast
|
package prog8.code.ast
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.IMemSizer
|
||||||
|
import prog8.code.core.IStringEncoding
|
||||||
|
import prog8.code.core.Position
|
||||||
|
import prog8.code.core.SourceCode
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
// New simplified AST for the code generator.
|
// New simplified AST for the code generator.
|
||||||
@ -11,16 +14,6 @@ sealed class PtNode(val position: Position) {
|
|||||||
val children = mutableListOf<PtNode>()
|
val children = mutableListOf<PtNode>()
|
||||||
lateinit var parent: PtNode
|
lateinit var parent: PtNode
|
||||||
|
|
||||||
fun printIndented(indent: Int) {
|
|
||||||
print(" ".repeat(indent))
|
|
||||||
print("${this.javaClass.simpleName} ")
|
|
||||||
printProperties()
|
|
||||||
println()
|
|
||||||
children.forEach { it.printIndented(indent+1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun printProperties()
|
|
||||||
|
|
||||||
fun add(child: PtNode) {
|
fun add(child: PtNode) {
|
||||||
children.add(child)
|
children.add(child)
|
||||||
child.parent = this
|
child.parent = this
|
||||||
@ -34,25 +27,30 @@ sealed class PtNode(val position: Position) {
|
|||||||
fun definingBlock() = findParentNode<PtBlock>(this)
|
fun definingBlock() = findParentNode<PtBlock>(this)
|
||||||
fun definingSub() = findParentNode<PtSub>(this)
|
fun definingSub() = findParentNode<PtSub>(this)
|
||||||
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
||||||
|
fun definingISub() = findParentNode<IPtSubroutine>(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtNodeGroup : PtNode(Position.DUMMY) {
|
sealed interface IPtStatementContainer
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
class PtNodeGroup : PtNode(Position.DUMMY), IPtStatementContainer
|
||||||
val scopedName: List<String> by lazy {
|
|
||||||
var namedParent: PtNode = this.parent
|
|
||||||
if(namedParent is PtProgram)
|
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
|
||||||
listOf(name)
|
// Note that as an exception, the 'name' is not read-only
|
||||||
else {
|
// but a var. This is to allow for cheap node renames.
|
||||||
while (namedParent !is PtNamedNode)
|
val scopedName: String
|
||||||
namedParent = namedParent.parent
|
get() {
|
||||||
namedParent.scopedName + name
|
var namedParent: PtNode = this.parent
|
||||||
|
return if(namedParent is PtProgram)
|
||||||
|
name
|
||||||
|
else {
|
||||||
|
while (namedParent !is PtNamedNode)
|
||||||
|
namedParent = namedParent.parent
|
||||||
|
namedParent.scopedName + "." + name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,10 +59,6 @@ class PtProgram(
|
|||||||
val memsizer: IMemSizer,
|
val memsizer: IMemSizer,
|
||||||
val encoding: IStringEncoding
|
val encoding: IStringEncoding
|
||||||
) : PtNode(Position.DUMMY) {
|
) : PtNode(Position.DUMMY) {
|
||||||
fun print() = printIndented(0)
|
|
||||||
override fun printProperties() {
|
|
||||||
print("'$name'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun allModuleDirectives(): Sequence<PtDirective> =
|
// fun allModuleDirectives(): Sequence<PtDirective> =
|
||||||
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
||||||
@ -73,32 +67,34 @@ class PtProgram(
|
|||||||
children.asSequence().filterIsInstance<PtBlock>()
|
children.asSequence().filterIsInstance<PtBlock>()
|
||||||
|
|
||||||
fun entrypoint(): PtSub? =
|
fun entrypoint(): PtSub? =
|
||||||
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && it.name == "start" } as PtSub?
|
allBlocks().firstOrNull { it.name == "main" || it.name=="p8b_main" }
|
||||||
|
?.children
|
||||||
|
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8s_start" || it.name=="p8b_main.p8s_start") } as PtSub?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtBlock(name: String,
|
class PtBlock(name: String,
|
||||||
val address: UInt?,
|
|
||||||
val library: Boolean,
|
val library: Boolean,
|
||||||
val forceOutput: Boolean,
|
val source: SourceCode, // taken from the module the block is defined in.
|
||||||
val alignment: BlockAlignment,
|
val options: Options,
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position), IPtStatementContainer {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BlockAlignment {
|
enum class BlockAlignment {
|
||||||
NONE,
|
NONE,
|
||||||
WORD,
|
WORD,
|
||||||
PAGE
|
PAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Options(val address: UInt? = null,
|
||||||
|
val forceOutput: Boolean = false,
|
||||||
|
val noSymbolPrefixing: Boolean = false,
|
||||||
|
val veraFxMuls: Boolean = false,
|
||||||
|
val ignoreUnused: Boolean = false,
|
||||||
|
val alignment: BlockAlignment = BlockAlignment.NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
@ -106,34 +102,16 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtBreakpoint(position: Position): PtNode(position) {
|
class PtBreakpoint(position: Position): PtNode(position)
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
|
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
|
||||||
override fun printProperties() {
|
|
||||||
print("filename=$file offset=$offset length=$length")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtNop(position: Position): PtNode(position) {
|
class PtNop(position: Position): PtNode(position)
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtScopeVarsDecls(position: Position): PtNode(position) {
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// find the parent node of a specific type or interface
|
// find the parent node of a specific type or interface
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package prog8.code.ast
|
package prog8.code.ast
|
||||||
|
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.*
|
||||||
import prog8.code.core.Encoding
|
|
||||||
import prog8.code.core.Position
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.round
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.truncate
|
||||||
|
|
||||||
|
|
||||||
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
|
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
|
||||||
@ -23,17 +22,30 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun isSameAs(other: PtExpression): Boolean {
|
infix fun isSameAs(other: PtExpression): Boolean {
|
||||||
return when(this) {
|
return when(this) {
|
||||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
is PtAddressOf -> {
|
||||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
if(other !is PtAddressOf)
|
||||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
return false
|
||||||
|
if (other.type!==type || !(other.identifier isSameAs identifier))
|
||||||
|
return false
|
||||||
|
if(other.children.size!=children.size)
|
||||||
|
return false
|
||||||
|
if(children.size==1)
|
||||||
|
return true
|
||||||
|
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
|
||||||
|
}
|
||||||
|
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
|
||||||
|
is PtBinaryExpression -> {
|
||||||
|
if(other !is PtBinaryExpression || other.operator!=operator)
|
||||||
|
false
|
||||||
|
else if(operator in AssociativeOperators)
|
||||||
|
(other.left isSameAs left && other.right isSameAs right) || (other.left isSameAs right && other.right isSameAs left)
|
||||||
|
else
|
||||||
|
other.left isSameAs left && other.right isSameAs right
|
||||||
|
}
|
||||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
|
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||||
@ -43,19 +55,102 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix fun isSameAs(target: PtAssignTarget): Boolean {
|
||||||
|
return when {
|
||||||
|
target.memory != null && this is PtMemoryByte-> {
|
||||||
|
target.memory!!.address isSameAs this.address
|
||||||
|
}
|
||||||
|
target.identifier != null && this is PtIdentifier -> {
|
||||||
|
this.name == target.identifier!!.name
|
||||||
|
}
|
||||||
|
target.array != null && this is PtArrayIndexer -> {
|
||||||
|
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index && this.splitWords==target.array!!.splitWords
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
|
||||||
|
|
||||||
|
fun isSimple(): Boolean {
|
||||||
|
return when(this) {
|
||||||
|
is PtAddressOf -> true
|
||||||
|
is PtArray -> true
|
||||||
|
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||||
|
is PtBinaryExpression -> false
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
when (name) {
|
||||||
|
in arrayOf("msb", "lsb", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple() }
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PtContainmentCheck -> false
|
||||||
|
is PtFunctionCall -> false
|
||||||
|
is PtIdentifier -> true
|
||||||
|
is PtMachineRegister -> true
|
||||||
|
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
|
||||||
|
is PtNumber -> true
|
||||||
|
is PtPrefix -> value.isSimple()
|
||||||
|
is PtRange -> true
|
||||||
|
is PtString -> true
|
||||||
|
is PtTypeCast -> value.isSimple()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun clone(): PtExpression {
|
||||||
|
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
|
||||||
|
orig.children.forEach { clone.add((it as PtExpression).clone()) }
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
when(this) {
|
||||||
|
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
|
||||||
|
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
|
||||||
|
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
|
||||||
|
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
|
||||||
|
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
|
||||||
|
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
|
||||||
|
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
|
||||||
|
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
|
||||||
|
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
|
||||||
|
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
|
||||||
|
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
|
||||||
|
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
|
||||||
|
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
|
||||||
|
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
|
||||||
|
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||||
val identifier: PtIdentifier
|
val identifier: PtIdentifier
|
||||||
get() = children.single() as PtIdentifier
|
get() = children[0] as PtIdentifier
|
||||||
|
val arrayIndexExpr: PtExpression?
|
||||||
|
get() = if(children.size==2) children[1] as PtExpression else null
|
||||||
|
|
||||||
|
val isFromArrayElement: Boolean
|
||||||
|
get() = children.size==2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) {
|
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
|
||||||
val variable: PtIdentifier
|
val variable: PtIdentifier
|
||||||
get() = children[0] as PtIdentifier
|
get() = children[0] as PtIdentifier
|
||||||
val index: PtExpression
|
val index: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
|
val splitWords: Boolean
|
||||||
|
get() = variable.type in SplitWordArrayTypes
|
||||||
|
|
||||||
|
val usesPointerVariable: Boolean
|
||||||
|
get() = variable.type==DataType.UWORD
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(elementType in NumericDatatypes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +161,9 @@ class PtArray(type: DataType, position: Position): PtExpression(type, position)
|
|||||||
return false
|
return false
|
||||||
return type==other.type && children == other.children
|
return type==other.type && children == other.children
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val size: Int
|
||||||
|
get() = children.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,9 +179,6 @@ class PtBuiltinFunctionCall(val name: String,
|
|||||||
|
|
||||||
val args: List<PtExpression>
|
val args: List<PtExpression>
|
||||||
get() = children.map { it as PtExpression }
|
get() = children.map { it as PtExpression }
|
||||||
override fun printProperties() {
|
|
||||||
print("$name void=$void noSideFx=$hasNoSideEffects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,10 +188,6 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val right: PtExpression
|
val right: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print("$operator -> $type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -108,7 +199,7 @@ class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, posit
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtFunctionCall(val functionName: List<String>,
|
class PtFunctionCall(val name: String,
|
||||||
val void: Boolean,
|
val void: Boolean,
|
||||||
type: DataType,
|
type: DataType,
|
||||||
position: Position) : PtExpression(type, position) {
|
position: Position) : PtExpression(type, position) {
|
||||||
@ -119,42 +210,41 @@ class PtFunctionCall(val functionName: List<String>,
|
|||||||
|
|
||||||
val args: List<PtExpression>
|
val args: List<PtExpression>
|
||||||
get() = children.map { it as PtExpression }
|
get() = children.map { it as PtExpression }
|
||||||
override fun printProperties() {
|
|
||||||
print("${functionName.joinToString(".")} void=$void")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) {
|
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
|
||||||
override fun printProperties() {
|
override fun toString(): String {
|
||||||
print("$ref --> $targetName $type")
|
return "[PtIdentifier:$name $type $position]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun copy() = PtIdentifier(name, type, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
||||||
val address: PtExpression
|
val address: PtExpression
|
||||||
get() = children.single() as PtExpression
|
get() = children.single() as PtExpression
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
|
||||||
|
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(type==DataType.BOOL)
|
if(type==DataType.BOOL)
|
||||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||||
if(type!=DataType.FLOAT) {
|
if(type!=DataType.FLOAT) {
|
||||||
val rounded = round(number)
|
val trunc = truncate(number)
|
||||||
if (rounded != number)
|
if (trunc != number)
|
||||||
throw IllegalArgumentException("refused rounding of float to avoid loss of precision @$position")
|
throw IllegalArgumentException("refused truncating of float to avoid loss of precision @$position")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print("$number ($type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(type, number)
|
override fun hashCode(): Int = Objects.hash(type, number)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -164,6 +254,8 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
|
|||||||
}
|
}
|
||||||
|
|
||||||
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
||||||
|
|
||||||
|
override fun toString() = "PtNumber:$type:$number"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -175,10 +267,6 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
|
|||||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||||
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(operator)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -190,15 +278,36 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
|
|||||||
val step: PtNumber
|
val step: PtNumber
|
||||||
get() = children[2] as PtNumber
|
get() = children[2] as PtNumber
|
||||||
|
|
||||||
override fun printProperties() {}
|
fun toConstantIntegerRange(): IntProgression? {
|
||||||
|
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||||
|
return when {
|
||||||
|
fromVal <= toVal -> when {
|
||||||
|
stepVal <= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == 1 -> fromVal..toVal
|
||||||
|
else -> fromVal..toVal step stepVal
|
||||||
|
}
|
||||||
|
else -> when {
|
||||||
|
stepVal >= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == -1 -> fromVal downTo toVal
|
||||||
|
else -> fromVal downTo toVal step abs(stepVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fromLv = from as? PtNumber
|
||||||
|
val toLv = to as? PtNumber
|
||||||
|
val stepLv = step as? PtNumber
|
||||||
|
if(fromLv==null || toLv==null || stepLv==null)
|
||||||
|
return null
|
||||||
|
val fromVal = fromLv.number.toInt()
|
||||||
|
val toVal = toLv.number.toInt()
|
||||||
|
val stepVal = stepLv.number.toInt()
|
||||||
|
return makeRange(fromVal, toVal, stepVal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$encoding:\"$value\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is PtString)
|
if(other==null || other !is PtString)
|
||||||
@ -214,12 +323,8 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// special node that isn't created from compiling user code, but used internally
|
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
|
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||||
override fun printProperties() {
|
|
||||||
print("reg=$register $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
||||||
|
172
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
172
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package prog8.code.ast
|
||||||
|
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
|
||||||
|
* passing it as a String to the specified receiver function.
|
||||||
|
*/
|
||||||
|
fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Unit) {
|
||||||
|
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||||
|
fun txt(node: PtNode): String {
|
||||||
|
return when(node) {
|
||||||
|
is PtAssignTarget -> "<target>"
|
||||||
|
is PtAssignment -> "<assign>"
|
||||||
|
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||||
|
is PtBreakpoint -> "%breakpoint"
|
||||||
|
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
|
||||||
|
is PtAddressOf -> "&"
|
||||||
|
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
||||||
|
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
||||||
|
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
val str = if(node.void) "void " else ""
|
||||||
|
str + node.name + "()"
|
||||||
|
}
|
||||||
|
is PtContainmentCheck -> "in"
|
||||||
|
is PtFunctionCall -> {
|
||||||
|
val str = if(node.void) "void " else ""
|
||||||
|
str + node.name + "()"
|
||||||
|
}
|
||||||
|
is PtIdentifier -> "${node.name} ${type(node.type)}"
|
||||||
|
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
|
||||||
|
is PtMemoryByte -> "@()"
|
||||||
|
is PtNumber -> {
|
||||||
|
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
|
||||||
|
"$numstr ${type(node.type)}"
|
||||||
|
}
|
||||||
|
is PtPrefix -> node.operator
|
||||||
|
is PtRange -> "<range>"
|
||||||
|
is PtString -> "\"${node.value.escape()}\""
|
||||||
|
is PtTypeCast -> "as ${node.type.name.lowercase()}"
|
||||||
|
is PtForLoop -> "for"
|
||||||
|
is PtIfElse -> "ifelse"
|
||||||
|
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
|
||||||
|
is PtInlineAssembly -> {
|
||||||
|
if(node.isIR)
|
||||||
|
"%ir {{ ...${node.assembly.length} characters... }}"
|
||||||
|
else
|
||||||
|
"%asm {{ ...${node.assembly.length} characters... }}"
|
||||||
|
}
|
||||||
|
is PtJump -> {
|
||||||
|
if(node.identifier!=null)
|
||||||
|
"goto ${node.identifier.name}"
|
||||||
|
else if(node.address!=null)
|
||||||
|
"goto ${node.address.toHex()}"
|
||||||
|
else
|
||||||
|
"???"
|
||||||
|
}
|
||||||
|
is PtAsmSub -> {
|
||||||
|
val params = node.parameters.map {
|
||||||
|
val register = it.first.registerOrPair
|
||||||
|
val statusflag = it.first.statusflag
|
||||||
|
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
|
||||||
|
}.joinToString(", ")
|
||||||
|
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
|
||||||
|
val returns = if (node.returns.isEmpty()) "" else {
|
||||||
|
"-> ${node.returns.map {
|
||||||
|
val register = it.first.registerOrPair
|
||||||
|
val statusflag = it.first.statusflag
|
||||||
|
"${it.second} @${register ?: statusflag}"}
|
||||||
|
.joinToString(", ")
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
val str = if (node.inline) "inline " else ""
|
||||||
|
if(node.address==null) {
|
||||||
|
str + "asmsub ${node.name}($params) $clobbers $returns"
|
||||||
|
} else {
|
||||||
|
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PtBlock -> {
|
||||||
|
val addr = if(node.options.address==null) "" else "@${node.options.address.toHex()}"
|
||||||
|
val align = if(node.options.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.options.alignment}"
|
||||||
|
"\nblock '${node.name}' $addr $align"
|
||||||
|
}
|
||||||
|
is PtConstant -> {
|
||||||
|
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
|
||||||
|
"const ${node.type.name.lowercase()} ${node.name} = $value"
|
||||||
|
}
|
||||||
|
is PtLabel -> "${node.name}:"
|
||||||
|
is PtMemMapped -> {
|
||||||
|
if(node.type in ArrayDatatypes) {
|
||||||
|
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
|
||||||
|
} else {
|
||||||
|
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
val params = node.parameters.map { "${it.type} ${it.name}" }.joinToString(", ")
|
||||||
|
var str = "sub ${node.name}($params) "
|
||||||
|
if(node.returntype!=null)
|
||||||
|
str += "-> ${node.returntype.name.lowercase()}"
|
||||||
|
str
|
||||||
|
}
|
||||||
|
is PtVariable -> {
|
||||||
|
val split = if(node.type in SplitWordArrayTypes) "@split" else ""
|
||||||
|
val str = if(node.arraySize!=null) {
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"${eltType.name.lowercase()}[${node.arraySize}] $split ${node.name}"
|
||||||
|
}
|
||||||
|
else if(node.type in ArrayDatatypes) {
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"${eltType.name.lowercase()}[] $split ${node.name}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
"${node.type.name.lowercase()} ${node.name}"
|
||||||
|
if(node.value!=null)
|
||||||
|
str + " = " + txt(node.value)
|
||||||
|
else
|
||||||
|
str
|
||||||
|
}
|
||||||
|
is PtNodeGroup -> "<group>"
|
||||||
|
is PtNop -> "nop"
|
||||||
|
is PtPostIncrDecr -> "<post> ${node.operator}"
|
||||||
|
is PtProgram -> "PROGRAM ${node.name}"
|
||||||
|
is PtRepeatLoop -> "repeat"
|
||||||
|
is PtReturn -> "return"
|
||||||
|
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
|
||||||
|
is PtWhen -> "when"
|
||||||
|
is PtWhenChoice -> {
|
||||||
|
if(node.isElse)
|
||||||
|
"else"
|
||||||
|
else
|
||||||
|
"->"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(root is PtProgram) {
|
||||||
|
output(txt(root))
|
||||||
|
root.children.forEach {
|
||||||
|
walkAst(it) { node, depth ->
|
||||||
|
val txt = txt(node)
|
||||||
|
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||||
|
if(!library || !skipLibraries) {
|
||||||
|
if (txt.isNotEmpty())
|
||||||
|
output(" ".repeat(depth) + txt(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
walkAst(root) { node, depth ->
|
||||||
|
val txt = txt(node)
|
||||||
|
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||||
|
if(!library || !skipLibraries) {
|
||||||
|
if (txt.isNotEmpty())
|
||||||
|
output(" ".repeat(depth) + txt(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
|
||||||
|
fun recurse(node: PtNode, depth: Int) {
|
||||||
|
act(node, depth)
|
||||||
|
node.children.forEach { recurse(it, depth+1) }
|
||||||
|
}
|
||||||
|
recurse(root, 0)
|
||||||
|
}
|
@ -3,87 +3,54 @@ package prog8.code.ast
|
|||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface IPtSubroutine {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
class PtAsmSub(
|
class PtAsmSub(
|
||||||
name: String,
|
name: String,
|
||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val clobbers: Set<CpuRegister>,
|
val clobbers: Set<CpuRegister>,
|
||||||
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
|
||||||
val returnTypes: List<DataType>, // TODO join with register as Pairs ?
|
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
|
||||||
val retvalRegisters: List<RegisterOrStatusflag>,
|
|
||||||
val inline: Boolean,
|
val inline: Boolean,
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position), IPtSubroutine
|
||||||
override fun printProperties() {
|
|
||||||
print("$name inline=$inline")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtSub(
|
class PtSub(
|
||||||
name: String,
|
name: String,
|
||||||
val parameters: List<PtSubroutineParameter>,
|
val parameters: List<PtSubroutineParameter>,
|
||||||
val returntype: DataType?,
|
val returntype: DataType?,
|
||||||
val inline: Boolean,
|
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer {
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// params and return value should not be str
|
// params and return value should not be str
|
||||||
if(parameters.any{ it.type !in NumericDatatypes })
|
if(parameters.any{ it.type !in NumericDatatypes })
|
||||||
throw AssemblyError("non-numeric parameter")
|
throw AssemblyError("non-numeric parameter")
|
||||||
if(returntype!=null && returntype !in NumericDatatypes)
|
if(returntype!=null && returntype !in NumericDatatypes)
|
||||||
throw AssemblyError("non-numeric returntype $returntype")
|
throw AssemblyError("non-numeric returntype $returntype")
|
||||||
|
parameters.forEach { it.parent=this }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) {
|
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
|
||||||
override fun printProperties() {
|
|
||||||
print("$type $name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtAssignment(position: Position) : PtNode(position) {
|
sealed interface IPtAssignment {
|
||||||
|
val children: MutableList<PtNode>
|
||||||
val target: PtAssignTarget
|
val target: PtAssignTarget
|
||||||
get() = children[0] as PtAssignTarget
|
get() = children[0] as PtAssignTarget
|
||||||
val value: PtExpression
|
val value: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
override fun printProperties() { }
|
|
||||||
|
|
||||||
val isInplaceAssign: Boolean by lazy {
|
|
||||||
val target = target.children.single() as PtExpression
|
|
||||||
when(val source = value) {
|
|
||||||
is PtArrayIndexer -> {
|
|
||||||
if(target is PtArrayIndexer && source.type==target.type) {
|
|
||||||
if(target.variable isSameAs source.variable) {
|
|
||||||
target.index isSameAs source.index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
|
|
||||||
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
|
||||||
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
|
||||||
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
|
||||||
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
|
|
||||||
is PtPrefix -> {
|
|
||||||
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
|
||||||
||
|
|
||||||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
|
|
||||||
}
|
|
||||||
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
|
||||||
is PtBinaryExpression ->
|
|
||||||
target isSameAs source.left
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
|
||||||
|
|
||||||
|
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
|
||||||
|
|
||||||
|
|
||||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||||
val identifier: PtIdentifier?
|
val identifier: PtIdentifier?
|
||||||
get() = children.single() as? PtIdentifier
|
get() = children.single() as? PtIdentifier
|
||||||
@ -102,7 +69,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {}
|
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -111,10 +78,6 @@ class PtConditionalBranch(val condition: BranchCondition, position: Position) :
|
|||||||
get() = children[0] as PtNodeGroup
|
get() = children[0] as PtNodeGroup
|
||||||
val falseScope: PtNodeGroup
|
val falseScope: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(condition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -125,31 +88,24 @@ class PtForLoop(position: Position) : PtNode(position) {
|
|||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[2] as PtNodeGroup
|
get() = children[2] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtIfElse(position: Position) : PtNode(position) {
|
class PtIfElse(position: Position) : PtNode(position) {
|
||||||
val condition: PtBinaryExpression
|
val condition: PtExpression
|
||||||
get() = children[0] as PtBinaryExpression
|
get() = children[0] as PtExpression
|
||||||
val ifScope: PtNodeGroup
|
val ifScope: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
val elseScope: PtNodeGroup
|
val elseScope: PtNodeGroup
|
||||||
get() = children[2] as PtNodeGroup
|
get() = children[2] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtJump(val identifier: PtIdentifier?,
|
class PtJump(val identifier: PtIdentifier?, // note: even ad-hoc labels are wrapped as an Identifier to simplify code. Just use dummy type and position.
|
||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val generatedLabel: String?,
|
|
||||||
position: Position) : PtNode(position) {
|
position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {
|
init {
|
||||||
identifier?.printProperties()
|
identifier?.let {it.parent = this }
|
||||||
if(address!=null) print(address.toHex())
|
|
||||||
if(generatedLabel!=null) print(generatedLabel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +113,6 @@ class PtJump(val identifier: PtIdentifier?,
|
|||||||
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
||||||
val target: PtAssignTarget
|
val target: PtAssignTarget
|
||||||
get() = children.single() as PtAssignTarget
|
get() = children.single() as PtAssignTarget
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(operator)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -169,8 +121,6 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -183,30 +133,26 @@ class PtReturn(position: Position) : PtNode(position) {
|
|||||||
else
|
else
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
|
sealed interface IPtVariable {
|
||||||
override fun printProperties() {
|
val name: String
|
||||||
print("$type $name")
|
val type: DataType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
|
||||||
|
init {
|
||||||
|
value?.let {it.parent=this}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) {
|
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||||
override fun printProperties() {
|
|
||||||
print("$type $name = $value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
|
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||||
override fun printProperties() {
|
|
||||||
print("&$type $name = ${address.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtWhen(position: Position) : PtNode(position) {
|
class PtWhen(position: Position) : PtNode(position) {
|
||||||
@ -214,8 +160,6 @@ class PtWhen(position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val choices: PtNodeGroup
|
val choices: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -224,5 +168,4 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtNodeGroup
|
get() = children[0] as PtNodeGroup
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
135
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
135
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package prog8.code.core
|
||||||
|
|
||||||
|
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||||
|
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||||
|
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||||
|
override fun toString(): String {
|
||||||
|
val paramConvs = params.mapIndexed { index, it ->
|
||||||
|
when {
|
||||||
|
it.reg!=null -> "$index:${it.reg}"
|
||||||
|
it.variable -> "$index:variable"
|
||||||
|
else -> "$index:???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val returnConv =
|
||||||
|
when {
|
||||||
|
returns.reg!=null -> returns.reg.toString()
|
||||||
|
returns.floatFac1 -> "floatFAC1"
|
||||||
|
else -> "<no returnvalue>"
|
||||||
|
}
|
||||||
|
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||||
|
|
||||||
|
class FSignature(val pure: Boolean, // does it have side effects?
|
||||||
|
val parameters: List<FParam>,
|
||||||
|
val returnType: DataType?) {
|
||||||
|
|
||||||
|
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
|
||||||
|
val returns: ReturnConvention = when (returnType) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(returnType, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
|
||||||
|
null -> ReturnConvention(null, null, false)
|
||||||
|
else -> {
|
||||||
|
// return type depends on arg type
|
||||||
|
when (val paramType = actualParamTypes.first()) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(paramType, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ReturnConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||||
|
actualParamTypes.size==1 -> {
|
||||||
|
// one parameter goes via register/registerpair
|
||||||
|
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ParamConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
CallConvention(listOf(paramConv), returns)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// multiple parameters go via variables
|
||||||
|
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
|
||||||
|
CallConvention(paramConvs, returns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||||
|
// this set of function have no return value and operate in-place:
|
||||||
|
"setlsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||||
|
"setmsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||||
|
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
// cmp returns a status in the carry flag, but not a proper return value
|
||||||
|
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
||||||
|
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||||
|
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
|
||||||
|
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
|
||||||
|
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||||
|
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
|
||||||
|
"abs__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
|
||||||
|
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
|
||||||
|
// normal functions follow:
|
||||||
|
"sizeof" to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE),
|
||||||
|
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
|
||||||
|
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
|
||||||
|
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
|
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
|
||||||
|
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"divmod__ubyte" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||||
|
"divmod__uword" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||||
|
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||||
|
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||||
|
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||||
|
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||||
|
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||||
|
"clamp" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), null),
|
||||||
|
"clamp__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||||
|
"clamp__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE)), FParam("minimum", arrayOf(DataType.UBYTE)), FParam("maximum", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"clamp__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD)), FParam("minimum", arrayOf(DataType.WORD)), FParam("maximum", arrayOf(DataType.WORD))), DataType.WORD),
|
||||||
|
"clamp__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD)), FParam("minimum", arrayOf(DataType.UWORD)), FParam("maximum", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"min" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null),
|
||||||
|
"min__byte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||||
|
"min__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"min__word" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD),
|
||||||
|
"min__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"max" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null),
|
||||||
|
"max__byte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||||
|
"max__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"max__word" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD),
|
||||||
|
"max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
|
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"peekf" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT),
|
||||||
|
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||||
|
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||||
|
"pokef" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null),
|
||||||
|
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||||
|
"rsave" to FSignature(false, emptyList(), null),
|
||||||
|
"rrestore" to FSignature(false, emptyList(), null),
|
||||||
|
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
)
|
||||||
|
|
||||||
|
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
|
@ -8,19 +8,32 @@ class CompilationOptions(val output: OutputType,
|
|||||||
val launcher: CbmPrgLauncherType,
|
val launcher: CbmPrgLauncherType,
|
||||||
val zeropage: ZeropageType,
|
val zeropage: ZeropageType,
|
||||||
val zpReserved: List<UIntRange>,
|
val zpReserved: List<UIntRange>,
|
||||||
|
val zpAllowed: List<UIntRange>,
|
||||||
val floats: Boolean,
|
val floats: Boolean,
|
||||||
val noSysInit: Boolean,
|
val noSysInit: Boolean,
|
||||||
val compTarget: ICompilationTarget,
|
val compTarget: ICompilationTarget,
|
||||||
// these are set later, based on command line arguments or options in the source code:
|
// these are set later, based on command line arguments or options in the source code:
|
||||||
var loadAddress: UInt,
|
var loadAddress: UInt,
|
||||||
var slowCodegenWarnings: Boolean = false,
|
var warnSymbolShadowing: Boolean = false,
|
||||||
var optimize: Boolean = false,
|
var optimize: Boolean = false,
|
||||||
var optimizeFloatExpressions: Boolean = false,
|
|
||||||
var dontReinitGlobals: Boolean = false,
|
|
||||||
var asmQuiet: Boolean = false,
|
var asmQuiet: Boolean = false,
|
||||||
var asmListfile: Boolean = false,
|
var asmListfile: Boolean = false,
|
||||||
|
var includeSourcelines: Boolean = false,
|
||||||
var experimentalCodegen: Boolean = false,
|
var experimentalCodegen: Boolean = false,
|
||||||
var evalStackBaseAddress: UInt? = null,
|
var varsHighBank: Int? = null,
|
||||||
|
var varsGolden: Boolean = false,
|
||||||
|
var slabsHighBank: Int? = null,
|
||||||
|
var slabsGolden: Boolean = false,
|
||||||
|
var splitWordArrays: Boolean = false,
|
||||||
|
var breakpointCpuInstruction: Boolean = false,
|
||||||
var outputDir: Path = Path(""),
|
var outputDir: Path = Path(""),
|
||||||
var symbolDefs: Map<String, String> = emptyMap()
|
var symbolDefs: Map<String, String> = emptyMap()
|
||||||
)
|
) {
|
||||||
|
init {
|
||||||
|
compTarget.machine.initializeMemoryAreas(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
package prog8.code.core
|
package prog8.code.core
|
||||||
|
|
||||||
enum class DataType {
|
enum class DataType {
|
||||||
UBYTE, // pass by value
|
UBYTE, // pass by value 8 bits unsigned
|
||||||
BYTE, // pass by value
|
BYTE, // pass by value 8 bits signed
|
||||||
UWORD, // pass by value
|
UWORD, // pass by value 16 bits unsigned
|
||||||
WORD, // pass by value
|
WORD, // pass by value 16 bits signed
|
||||||
FLOAT, // pass by value
|
LONG, // pass by value 32 bits signed
|
||||||
BOOL, // pass by value
|
FLOAT, // pass by value machine dependent
|
||||||
|
BOOL, // pass by value bit 0 of a 8 bit byte
|
||||||
STR, // pass by reference
|
STR, // pass by reference
|
||||||
ARRAY_UB, // pass by reference
|
ARRAY_UB, // pass by reference
|
||||||
ARRAY_B, // pass by reference
|
ARRAY_B, // pass by reference
|
||||||
ARRAY_UW, // pass by reference
|
ARRAY_UW, // pass by reference
|
||||||
|
ARRAY_UW_SPLIT, // pass by reference, lo/hi byte split
|
||||||
ARRAY_W, // pass by reference
|
ARRAY_W, // pass by reference
|
||||||
|
ARRAY_W_SPLIT, // pass by reference, lo/hi byte split
|
||||||
ARRAY_F, // pass by reference
|
ARRAY_F, // pass by reference
|
||||||
ARRAY_BOOL, // pass by reference
|
ARRAY_BOOL, // pass by reference
|
||||||
UNDEFINED;
|
UNDEFINED;
|
||||||
@ -21,11 +24,12 @@ enum class DataType {
|
|||||||
*/
|
*/
|
||||||
infix fun isAssignableTo(targetType: DataType) =
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
when(this) {
|
when(this) {
|
||||||
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
|
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
|
||||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
|
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
|
||||||
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
|
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
|
||||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
|
||||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
|
||||||
|
LONG -> targetType.oneOf(LONG, FLOAT)
|
||||||
FLOAT -> targetType.oneOf(FLOAT)
|
FLOAT -> targetType.oneOf(FLOAT)
|
||||||
STR -> targetType.oneOf(STR, UWORD)
|
STR -> targetType.oneOf(STR, UWORD)
|
||||||
in ArrayDatatypes -> targetType == this
|
in ArrayDatatypes -> targetType == this
|
||||||
@ -39,7 +43,8 @@ enum class DataType {
|
|||||||
this == other -> false
|
this == other -> false
|
||||||
this in ByteDatatypes -> false
|
this in ByteDatatypes -> false
|
||||||
this in WordDatatypes -> other in ByteDatatypes
|
this in WordDatatypes -> other in ByteDatatypes
|
||||||
this== STR && other== UWORD || this== UWORD && other== STR -> false
|
this == LONG -> other in ByteDatatypes+WordDatatypes
|
||||||
|
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +79,13 @@ enum class RegisterOrPair {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val names by lazy { values().map { it.toString()} }
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||||
|
return when(cpu) {
|
||||||
|
CpuRegister.A -> A
|
||||||
|
CpuRegister.X -> X
|
||||||
|
CpuRegister.Y -> Y
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asCpuRegister(): CpuRegister = when(this) {
|
fun asCpuRegister(): CpuRegister = when(this) {
|
||||||
@ -108,23 +120,25 @@ enum class BranchCondition {
|
|||||||
PL, // PL == POS
|
PL, // PL == POS
|
||||||
POS,
|
POS,
|
||||||
VS,
|
VS,
|
||||||
VC,
|
VC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
||||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
|
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
|
||||||
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
|
||||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
|
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
|
||||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||||
|
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
|
||||||
val IterableDatatypes = arrayOf(
|
val IterableDatatypes = arrayOf(
|
||||||
DataType.STR,
|
DataType.STR,
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||||
|
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT,
|
||||||
DataType.ARRAY_F, DataType.ARRAY_BOOL
|
DataType.ARRAY_F, DataType.ARRAY_BOOL
|
||||||
)
|
)
|
||||||
val PassByValueDatatypes = NumericDatatypes
|
val PassByValueDatatypes = NumericDatatypes
|
||||||
@ -135,6 +149,8 @@ val ArrayToElementTypes = mapOf(
|
|||||||
DataType.ARRAY_UB to DataType.UBYTE,
|
DataType.ARRAY_UB to DataType.UBYTE,
|
||||||
DataType.ARRAY_W to DataType.WORD,
|
DataType.ARRAY_W to DataType.WORD,
|
||||||
DataType.ARRAY_UW to DataType.UWORD,
|
DataType.ARRAY_UW to DataType.UWORD,
|
||||||
|
DataType.ARRAY_W_SPLIT to DataType.WORD,
|
||||||
|
DataType.ARRAY_UW_SPLIT to DataType.UWORD,
|
||||||
DataType.ARRAY_F to DataType.FLOAT,
|
DataType.ARRAY_F to DataType.FLOAT,
|
||||||
DataType.ARRAY_BOOL to DataType.BOOL
|
DataType.ARRAY_BOOL to DataType.BOOL
|
||||||
)
|
)
|
||||||
@ -144,8 +160,10 @@ val ElementToArrayTypes = mapOf(
|
|||||||
DataType.WORD to DataType.ARRAY_W,
|
DataType.WORD to DataType.ARRAY_W,
|
||||||
DataType.UWORD to DataType.ARRAY_UW,
|
DataType.UWORD to DataType.ARRAY_UW,
|
||||||
DataType.FLOAT to DataType.ARRAY_F,
|
DataType.FLOAT to DataType.ARRAY_F,
|
||||||
DataType.BOOL to DataType.ARRAY_BOOL
|
DataType.BOOL to DataType.ARRAY_BOOL,
|
||||||
|
DataType.STR to DataType.ARRAY_UW // array of str is just an array of pointers
|
||||||
)
|
)
|
||||||
|
|
||||||
val Cx16VirtualRegisters = arrayOf(
|
val Cx16VirtualRegisters = arrayOf(
|
||||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||||
@ -153,6 +171,10 @@ val Cx16VirtualRegisters = arrayOf(
|
|||||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val CpuRegisters = setOf(
|
||||||
|
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||||
|
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
enum class OutputType {
|
enum class OutputType {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package prog8.code.core
|
|
||||||
|
|
||||||
interface IAssemblyGenerator {
|
|
||||||
fun compileToAssembly(): IAssemblyProgram?
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAssemblyProgram {
|
|
||||||
val name: String
|
|
||||||
fun assemble(options: CompilationOptions): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
|
17
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
17
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package prog8.code.core
|
||||||
|
|
||||||
|
import prog8.code.SymbolTable
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
|
|
||||||
|
interface ICodeGeneratorBackend {
|
||||||
|
fun generate(program: PtProgram,
|
||||||
|
symbolTable: SymbolTable,
|
||||||
|
options: CompilationOptions,
|
||||||
|
errors: IErrorReporter): IAssemblyProgram?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IAssemblyProgram {
|
||||||
|
val name: String
|
||||||
|
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||||
|
}
|
@ -3,9 +3,7 @@ package prog8.code.core
|
|||||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||||
val name: String
|
val name: String
|
||||||
val machine: IMachineDefinition
|
val machine: IMachineDefinition
|
||||||
val supportedEncodings: Set<Encoding>
|
|
||||||
val defaultEncoding: Encoding
|
|
||||||
|
|
||||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package prog8.code.core
|
|||||||
interface IErrorReporter {
|
interface IErrorReporter {
|
||||||
fun err(msg: String, position: Position)
|
fun err(msg: String, position: Position)
|
||||||
fun warn(msg: String, position: Position)
|
fun warn(msg: String, position: Position)
|
||||||
|
fun info(msg: String, position: Position)
|
||||||
|
fun undefined(symbol: List<String>, position: Position)
|
||||||
fun noErrors(): Boolean
|
fun noErrors(): Boolean
|
||||||
fun report()
|
fun report()
|
||||||
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
|
fun finalizeNumErrors(numErrors: Int, numWarnings: Int, numInfos: Int) {
|
||||||
if(numErrors>0)
|
if(numErrors>0)
|
||||||
throw ErrorsReportedException("There are $numErrors errors and $numWarnings warnings.")
|
throw ErrorsReportedException("There are $numErrors errors, $numWarnings warnings, and $numInfos infos.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ interface IMachineDefinition {
|
|||||||
val FLOAT_MAX_NEGATIVE: Double
|
val FLOAT_MAX_NEGATIVE: Double
|
||||||
val FLOAT_MAX_POSITIVE: Double
|
val FLOAT_MAX_POSITIVE: Double
|
||||||
val FLOAT_MEM_SIZE: Int
|
val FLOAT_MEM_SIZE: Int
|
||||||
var ESTACK_LO: UInt
|
|
||||||
var ESTACK_HI: UInt
|
|
||||||
val PROGRAM_LOAD_ADDRESS : UInt
|
val PROGRAM_LOAD_ADDRESS : UInt
|
||||||
|
val BSSHIGHRAM_START: UInt
|
||||||
|
val BSSHIGHRAM_END: UInt
|
||||||
|
val BSSGOLDENRAM_START: UInt
|
||||||
|
val BSSGOLDENRAM_END: UInt
|
||||||
|
|
||||||
val cpu: CpuType
|
val cpu: CpuType
|
||||||
var zeropage: Zeropage
|
var zeropage: Zeropage
|
||||||
@ -27,11 +29,4 @@ interface IMachineDefinition {
|
|||||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||||
fun isIOAddress(address: UInt): Boolean
|
fun isIOAddress(address: UInt): Boolean
|
||||||
fun overrideEvalStack(evalStackBaseAddress: UInt) {
|
|
||||||
require(evalStackBaseAddress and 255u == 0u)
|
|
||||||
ESTACK_LO = evalStackBaseAddress
|
|
||||||
ESTACK_HI = evalStackBaseAddress + 256u
|
|
||||||
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ enum class Encoding(val prefix: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IStringEncoding {
|
interface IStringEncoding {
|
||||||
|
val defaultEncoding: Encoding
|
||||||
|
|
||||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||||
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class MemAllocationError(message: String) : Exception(message)
|
|||||||
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||||
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||||
|
|
||||||
abstract fun allocate(name: List<String>,
|
abstract fun allocate(name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
@ -29,7 +29,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
|
|
||||||
// the variables allocated into Zeropage.
|
// the variables allocated into Zeropage.
|
||||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||||
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
|
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||||
|
|
||||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||||
|
|
||||||
@ -42,6 +42,13 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun retainAllowed() {
|
||||||
|
synchronized(this) {
|
||||||
|
for(allowed in options.zpAllowed)
|
||||||
|
free.retainAll { it in allowed }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||||
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||||
fun hasWordAvailable(): Boolean {
|
fun hasWordAvailable(): Boolean {
|
||||||
@ -51,7 +58,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allocate(name: List<String>,
|
override fun allocate(name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
@ -107,7 +114,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
|
|
||||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||||
|
|
||||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
|
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String): UInt {
|
||||||
require(size>=0)
|
require(size>=0)
|
||||||
free.removeAll(address until address+size.toUInt())
|
free.removeAll(address until address+size.toUInt())
|
||||||
if(name.isNotEmpty()) {
|
if(name.isNotEmpty()) {
|
||||||
@ -131,11 +138,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: this class is not yet used
|
||||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||||
private var nextLocation: UInt = region.first
|
private var nextLocation: UInt = region.first
|
||||||
|
|
||||||
override fun allocate(
|
override fun allocate(
|
||||||
name: List<String>,
|
name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package prog8.code.core
|
package prog8.code.core
|
||||||
|
|
||||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
|
||||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
|
||||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||||
|
|
||||||
fun invertedComparisonOperator(operator: String) =
|
fun invertedComparisonOperator(operator: String) =
|
||||||
when (operator) {
|
when (operator) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package prog8.code.core
|
package prog8.code.core
|
||||||
|
|
||||||
|
import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX
|
||||||
import java.nio.file.InvalidPathException
|
import java.nio.file.InvalidPathException
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.absolute
|
||||||
@ -7,6 +8,10 @@ import kotlin.io.path.absolute
|
|||||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||||
fun toClickableStr(): String {
|
fun toClickableStr(): String {
|
||||||
|
if(this===DUMMY)
|
||||||
|
return ""
|
||||||
|
if(file.startsWith(LIBRARYFILEPREFIX))
|
||||||
|
return "$file:$line:$startCol:"
|
||||||
return try {
|
return try {
|
||||||
val path = Path(file).absolute().normalize().toString()
|
val path = Path(file).absolute().normalize().toString()
|
||||||
"file://$path:$line:$startCol:"
|
"file://$path:$line:$startCol:"
|
||||||
|
@ -3,6 +3,7 @@ package prog8.code.core
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.text.Normalizer
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.readText
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
@ -54,12 +55,14 @@ sealed class SourceCode {
|
|||||||
/**
|
/**
|
||||||
* filename prefix to designate library files that will be retreived from internal resources rather than disk
|
* filename prefix to designate library files that will be retreived from internal resources rather than disk
|
||||||
*/
|
*/
|
||||||
const val libraryFilePrefix = "library:"
|
const val LIBRARYFILEPREFIX = "library:"
|
||||||
const val stringSourcePrefix = "string:"
|
const val STRINGSOURCEPREFIX = "string:"
|
||||||
val curdir: Path = Path(".").toAbsolutePath()
|
val curdir: Path = Path(".").toAbsolutePath()
|
||||||
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
|
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
|
||||||
fun isRegularFilesystemPath(pathString: String) =
|
fun isRegularFilesystemPath(pathString: String) =
|
||||||
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
|
!(pathString.startsWith(LIBRARYFILEPREFIX) || pathString.startsWith(STRINGSOURCEPREFIX))
|
||||||
|
|
||||||
|
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,7 +72,7 @@ sealed class SourceCode {
|
|||||||
class Text(override val text: String): SourceCode() {
|
class Text(override val text: String): SourceCode() {
|
||||||
override val isFromResources = false
|
override val isFromResources = false
|
||||||
override val isFromFilesystem = false
|
override val isFromFilesystem = false
|
||||||
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}"
|
override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}"
|
||||||
override val name = "<unnamed-text>"
|
override val name = "<unnamed-text>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +95,7 @@ sealed class SourceCode {
|
|||||||
val normalized = path.normalize()
|
val normalized = path.normalize()
|
||||||
origin = relative(normalized).toString()
|
origin = relative(normalized).toString()
|
||||||
try {
|
try {
|
||||||
text = normalized.readText()
|
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
|
||||||
name = normalized.toFile().nameWithoutExtension
|
name = normalized.toFile().nameWithoutExtension
|
||||||
} catch (nfx: java.nio.file.NoSuchFileException) {
|
} catch (nfx: java.nio.file.NoSuchFileException) {
|
||||||
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
|
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
|
||||||
@ -110,7 +113,7 @@ sealed class SourceCode {
|
|||||||
|
|
||||||
override val isFromResources = true
|
override val isFromResources = true
|
||||||
override val isFromFilesystem = false
|
override val isFromFilesystem = false
|
||||||
override val origin = "$libraryFilePrefix$normalized"
|
override val origin = "$LIBRARYFILEPREFIX$normalized"
|
||||||
override val text: String
|
override val text: String
|
||||||
override val name: String
|
override val name: String
|
||||||
|
|
||||||
@ -124,7 +127,7 @@ sealed class SourceCode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
||||||
text = stream!!.reader().use { it.readText() }
|
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
|
||||||
name = Path(pathString).toFile().nameWithoutExtension
|
name = Path(pathString).toFile().nameWithoutExtension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,3 +142,33 @@ sealed class SourceCode {
|
|||||||
override val text: String = "<generated code node, no text representation>"
|
override val text: String = "<generated code node, no text representation>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object SourceLineCache {
|
||||||
|
private val cache = mutableMapOf<String, List<String>>()
|
||||||
|
|
||||||
|
private fun getCachedFile(file: String): List<String> {
|
||||||
|
val existing = cache[file]
|
||||||
|
if(existing!=null)
|
||||||
|
return existing
|
||||||
|
if (SourceCode.isRegularFilesystemPath(file)) {
|
||||||
|
val source = SourceCode.File(Path(file))
|
||||||
|
cache[file] = source.text.split('\n', '\r').map { it.trim() }
|
||||||
|
return cache.getValue(file)
|
||||||
|
} else if(file.startsWith(SourceCode.LIBRARYFILEPREFIX)) {
|
||||||
|
val source = SourceCode.Resource(file.drop(SourceCode.LIBRARYFILEPREFIX.length))
|
||||||
|
cache[file] = source.text.split('\n', '\r').map { it.trim()}
|
||||||
|
return cache.getValue(file)
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retrieveLine(position: Position): String? {
|
||||||
|
if (position.line>0) {
|
||||||
|
val lines = getCachedFile(position.file)
|
||||||
|
if(lines.isNotEmpty())
|
||||||
|
return lines[position.line-1]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
127
codeCore/src/prog8/code/optimize/Optimizer.kt
Normal file
127
codeCore/src/prog8/code/optimize/Optimizer.kt
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package prog8.code.optimize
|
||||||
|
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
|
||||||
|
if (!options.optimize)
|
||||||
|
return
|
||||||
|
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
|
||||||
|
// keep rolling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
|
||||||
|
fun recurse(node: PtNode, depth: Int) {
|
||||||
|
if(act(node, depth))
|
||||||
|
node.children.forEach { recurse(it, depth+1) }
|
||||||
|
}
|
||||||
|
recurse(root, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
|
||||||
|
|
||||||
|
fun extractableSubExpr(expr: PtExpression): Boolean {
|
||||||
|
val result = if(expr is PtBinaryExpression)
|
||||||
|
expr.type !in ByteDatatypes ||
|
||||||
|
!expr.left.isSimple() ||
|
||||||
|
!expr.right.isSimple() ||
|
||||||
|
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
|
||||||
|
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
!expr.isSimple()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each Binaryexpression, recurse to find a common subexpression pair therein.
|
||||||
|
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
|
||||||
|
walkAst(program) { node: PtNode, depth: Int ->
|
||||||
|
if(node is PtBinaryExpression) {
|
||||||
|
val subExpressions = mutableListOf<PtExpression>()
|
||||||
|
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
|
||||||
|
if (subNode is PtExpression) {
|
||||||
|
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
|
||||||
|
if (subNode is PtExpression) {
|
||||||
|
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
|
||||||
|
outer@for (first in subExpressions) {
|
||||||
|
for (second in subExpressions) {
|
||||||
|
if (first!==second && first isSameAs second) {
|
||||||
|
commons[node] = first to second
|
||||||
|
break@outer // do only 1 replacement at a time per binaryexpression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else true
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace common subexpressions by a temp variable that is assigned only once.
|
||||||
|
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
|
||||||
|
commons.forEach { binexpr, (occurrence1, occurrence2) ->
|
||||||
|
val (stmtContainer, stmt) = findContainingStatements(binexpr)
|
||||||
|
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
|
||||||
|
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
|
||||||
|
val containerScopedName = findScopeName(stmtContainer)
|
||||||
|
val tempvarName = "subexprvar_line${binexpr.position.line}_${binexpr.hashCode().toUInt()}"
|
||||||
|
// TODO: some tempvars could be reused, if they are from different lines
|
||||||
|
|
||||||
|
val datatype = occurrence1.type
|
||||||
|
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
|
||||||
|
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
|
||||||
|
occurrence1.parent.children[occurrence1idx] = singleReplacement1
|
||||||
|
singleReplacement1.parent = occurrence1.parent
|
||||||
|
occurrence2.parent.children[occurrence2idx] = singleReplacement2
|
||||||
|
singleReplacement2.parent = occurrence2.parent
|
||||||
|
|
||||||
|
val tempassign = PtAssignment(binexpr.position).also { assign ->
|
||||||
|
assign.add(PtAssignTarget(binexpr.position).also { tgt->
|
||||||
|
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
|
||||||
|
})
|
||||||
|
assign.add(occurrence1)
|
||||||
|
occurrence1.parent = assign
|
||||||
|
}
|
||||||
|
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
|
||||||
|
tempassign.parent = stmtContainer
|
||||||
|
|
||||||
|
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.DONTCARE, null, null, binexpr.position)
|
||||||
|
stmtContainer.add(0, tempvar)
|
||||||
|
tempvar.parent = stmtContainer
|
||||||
|
|
||||||
|
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commons.size
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun findScopeName(node: PtNode): String {
|
||||||
|
var parent=node
|
||||||
|
while(parent !is PtNamedNode)
|
||||||
|
parent = parent.parent
|
||||||
|
return parent.scopedName
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
|
||||||
|
var parent = node.parent
|
||||||
|
var child = node
|
||||||
|
while(true) {
|
||||||
|
if(parent is IPtStatementContainer) {
|
||||||
|
return parent to child
|
||||||
|
}
|
||||||
|
child=parent
|
||||||
|
parent=parent.parent
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ import prog8.code.target.atari.AtariMachineDefinition
|
|||||||
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val machine = AtariMachineDefinition()
|
override val machine = AtariMachineDefinition()
|
||||||
override val supportedEncodings = setOf(Encoding.ATASCII)
|
|
||||||
override val defaultEncoding = Encoding.ATASCII
|
override val defaultEncoding = Encoding.ATASCII
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -19,7 +18,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
|||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
else -> Int.MIN_VALUE
|
else -> throw IllegalArgumentException("invalid datatype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
|
|||||||
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val machine = C128MachineDefinition()
|
override val machine = C128MachineDefinition()
|
||||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
|
||||||
override val defaultEncoding = Encoding.PETSCII
|
override val defaultEncoding = Encoding.PETSCII
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -11,10 +11,11 @@ import prog8.code.target.cbm.CbmMemorySizer
|
|||||||
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val machine = C64MachineDefinition()
|
override val machine = C64MachineDefinition()
|
||||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
|
||||||
override val defaultEncoding = Encoding.PETSCII
|
override val defaultEncoding = Encoding.PETSCII
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NAME = "c64"
|
const val NAME = "c64"
|
||||||
|
|
||||||
|
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import prog8.code.target.cx16.CX16MachineDefinition
|
|||||||
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val machine = CX16MachineDefinition()
|
override val machine = CX16MachineDefinition()
|
||||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
|
|
||||||
override val defaultEncoding = Encoding.PETSCII
|
override val defaultEncoding = Encoding.PETSCII
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -10,6 +10,8 @@ import prog8.code.target.cbm.PetsciiEncoding
|
|||||||
|
|
||||||
|
|
||||||
object Encoder: IStringEncoding {
|
object Encoder: IStringEncoding {
|
||||||
|
override val defaultEncoding: Encoding = Encoding.ISO
|
||||||
|
|
||||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||||
val coded = when(encoding) {
|
val coded = when(encoding) {
|
||||||
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
|
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
|
||||||
@ -23,7 +25,7 @@ object Encoder: IStringEncoding {
|
|||||||
success = { it }
|
success = { it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
val decoded = when(encoding) {
|
val decoded = when(encoding) {
|
||||||
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
||||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||||
|
19
codeCore/src/prog8/code/target/PETTarget.kt
Normal file
19
codeCore/src/prog8/code/target/PETTarget.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package prog8.code.target
|
||||||
|
|
||||||
|
import prog8.code.core.Encoding
|
||||||
|
import prog8.code.core.ICompilationTarget
|
||||||
|
import prog8.code.core.IMemSizer
|
||||||
|
import prog8.code.core.IStringEncoding
|
||||||
|
import prog8.code.target.cbm.CbmMemorySizer
|
||||||
|
import prog8.code.target.pet.PETMachineDefinition
|
||||||
|
|
||||||
|
|
||||||
|
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||||
|
override val name = NAME
|
||||||
|
override val machine = PETMachineDefinition()
|
||||||
|
override val defaultEncoding = Encoding.PETSCII
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NAME = "pet32"
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import prog8.code.target.virtual.VirtualMachineDefinition
|
|||||||
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||||
override val name = NAME
|
override val name = NAME
|
||||||
override val machine = VirtualMachineDefinition()
|
override val machine = VirtualMachineDefinition()
|
||||||
override val supportedEncodings = setOf(Encoding.ISO)
|
|
||||||
override val defaultEncoding = Encoding.ISO
|
override val defaultEncoding = Encoding.ISO
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -18,7 +17,7 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
|||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
else -> Int.MIN_VALUE
|
else -> throw IllegalArgumentException("invalid datatype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,15 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = 6
|
override val FLOAT_MEM_SIZE = 6
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
override val BSSHIGHRAM_START = 0u // TODO
|
||||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
|
override val BSSHIGHRAM_END = 0u // TODO
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
override val BSSGOLDENRAM_START = 0u // TODO
|
||||||
|
override val BSSGOLDENRAM_END = 0u // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.output == OutputType.XEX)
|
return if (compilerOptions.output == OutputType.XEX)
|
||||||
|
@ -13,6 +13,10 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (options.floats) {
|
||||||
|
throw InternalCompilerException("Atari target doesn't yet support floating point routines")
|
||||||
|
}
|
||||||
|
|
||||||
if (options.floats && options.zeropage !in arrayOf(
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE,
|
ZeropageType.BASICSAFE,
|
||||||
@ -39,11 +43,12 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
retainAllowed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allocateCx16VirtualRegisters() {
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.c128
|
package prog8.code.target.c128
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -14,9 +15,11 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
override val BSSHIGHRAM_START = 0u // TODO
|
||||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
override val BSSHIGHRAM_END = 0u // TODO
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
override val BSSGOLDENRAM_START = 0u // TODO
|
||||||
|
override val BSSGOLDENRAM_END = 0u // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -36,7 +39,7 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("\nStarting C-128 emulator x128...")
|
println("\nStarting C-128 emulator x128...")
|
||||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||||
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
|
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
@ -6,15 +6,22 @@ import prog8.code.core.Zeropage
|
|||||||
import prog8.code.core.ZeropageType
|
import prog8.code.core.ZeropageType
|
||||||
|
|
||||||
|
|
||||||
|
// reference: "Mapping the C128" zero page chapter.
|
||||||
|
|
||||||
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
override val SCRATCH_B1 = 0x9bu // temp storage for a single byte
|
override val SCRATCH_B1 = 0x74u // temp storage for a single byte
|
||||||
override val SCRATCH_REG = 0x9cu // temp storage for a register, must be B1+1
|
override val SCRATCH_REG = 0x75u // temp storage for a register, must be B1+1
|
||||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (options.floats) {
|
||||||
|
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
|
||||||
|
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
|
||||||
|
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
|
||||||
|
}
|
||||||
|
|
||||||
if (options.floats && options.zeropage !in arrayOf(
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE,
|
ZeropageType.BASICSAFE,
|
||||||
@ -24,25 +31,45 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
|
|
||||||
when (options.zeropage) {
|
when (options.zeropage) {
|
||||||
ZeropageType.FULL -> {
|
ZeropageType.FULL -> {
|
||||||
// TODO all c128 usable zero page locations, except the ones used by the system's IRQ routine
|
// $00/$01 are data port IO registers, // $02-$09 are storage locations for JSRFAR and such
|
||||||
free.addAll(0x0au..0xffu) // TODO c128 what about $02-$09?
|
free.addAll(0x0au..0xffu)
|
||||||
// TODO c128 free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
free.removeAll(listOf(0x90u, 0x91u, 0xa0u, 0xa1u, 0xa2u, 0xc0u, 0xccu, 0xcdu, 0xd0u, 0xd1u, 0xd2u, 0xd3u, 0xd4u, 0xd5u, 0xf7u)) // these are updated/used by IRQ
|
||||||
|
}
|
||||||
|
ZeropageType.KERNALSAFE -> {
|
||||||
|
free.addAll(0x0au..0x8fu) // BASIC variables
|
||||||
|
free.addAll(listOf(0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu,
|
||||||
|
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u))
|
||||||
}
|
}
|
||||||
ZeropageType.KERNALSAFE,
|
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE -> {
|
ZeropageType.BASICSAFE -> {
|
||||||
free.clear() // TODO c128 usable zero page addresses
|
free.addAll(listOf(0x0bu, 0x0cu, 0x0du, 0x0eu, 0x0fu, 0x10u, 0x11u, 0x12u, 0x16u, 0x17u, 0x18u, 0x19u, 0x1au))
|
||||||
|
free.addAll(0x1bu..0x23u)
|
||||||
|
free.addAll(listOf(0x3fu, 0x40u, 0x41u, 0x42u, 0x43u, 0x44u, 0x47u, 0x48u, 0x49u, 0x4au, 0x4bu, 0x4cu, 0x4fu,
|
||||||
|
0x55u, 0x56u, 0x57u, 0x58u,
|
||||||
|
0x74u, 0x75u, 0x78u, 0x80u, 0x83u, 0x87u, 0x88u, 0x89u, 0x8au, 0x8bu, 0x8cu, 0x8du, 0x8eu, 0x8fu,
|
||||||
|
0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu,
|
||||||
|
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
|
||||||
|
))
|
||||||
|
|
||||||
|
// if(options.zeropage==ZeropageType.BASICSAFE) {
|
||||||
|
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
|
||||||
|
free.addAll(listOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||||
|
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||||
|
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||||
|
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
ZeropageType.DONTUSE -> {
|
ZeropageType.DONTUSE -> {
|
||||||
free.clear() // don't use zeropage at all
|
free.clear() // don't use zeropage at all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
retainAllowed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allocateCx16VirtualRegisters() {
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.c64
|
package prog8.code.target.c64
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -15,9 +16,11 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
override val BSSHIGHRAM_START = 0xc000u
|
||||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
override val BSSHIGHRAM_END = 0xcfffu
|
||||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
override val BSSGOLDENRAM_START = 0u
|
||||||
|
override val BSSGOLDENRAM_END = 0u
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
for(emulator in listOf("x64sc", "x64")) {
|
for(emulator in listOf("x64sc", "x64")) {
|
||||||
println("\nStarting C-64 emulator $emulator...")
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
@ -57,7 +60,7 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = C64Zeropage(compilerOptions)
|
zeropage = C64Zeropage(compilerOptions)
|
||||||
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
|
golden = GoldenRam(compilerOptions, 0xc000u until 0xd000u)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
// remove the zeropage locations used for floating point operations from the free list
|
// remove the zeropage locations used for floating point operations from the free list
|
||||||
free.removeAll(listOf(
|
free.removeAll(listOf(
|
||||||
0x03, 0x04, 0x10, 0x11, 0x12,
|
0x03, 0x04, 0x05, 0x06, 0x10, 0x11, 0x12,
|
||||||
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
).map{it.toUInt()})
|
).map{it.toUInt()})
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
if(options.zeropage != ZeropageType.DONTUSE) {
|
||||||
// add the free Zp addresses
|
// add the free Zp addresses
|
||||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||||
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||||
} else {
|
} else {
|
||||||
// don't use the zeropage at all
|
// don't use the zeropage at all
|
||||||
@ -65,9 +65,9 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
@ -75,6 +75,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||||
allocateCx16VirtualRegisters()
|
allocateCx16VirtualRegisters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retainAllowed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allocateCx16VirtualRegisters() {
|
override fun allocateCx16VirtualRegisters() {
|
||||||
@ -83,12 +85,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||||
for(reg in 0..15) {
|
for(reg in 0..15) {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
free.remove((4+reg*2).toUInt())
|
free.remove((4+reg*2).toUInt())
|
||||||
free.remove((5+reg*2).toUInt())
|
free.remove((5+reg*2).toUInt())
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ object AtasciiEncoding {
|
|||||||
return Ok(mapped)
|
return Ok(mapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||||
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
|
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ internal object CbmMemorySizer: IMemSizer {
|
|||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||||
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
|
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
|
||||||
else -> Int.MIN_VALUE
|
else -> throw IllegalArgumentException("invalid datatype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ object IsoEncoding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||||
return try {
|
return try {
|
||||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||||
} catch (ce: CharConversionException) {
|
} catch (ce: CharConversionException) {
|
||||||
|
@ -24,7 +24,7 @@ object PetsciiEncoding {
|
|||||||
'\ufffe', // 0x0A -> UNDEFINED
|
'\ufffe', // 0x0A -> UNDEFINED
|
||||||
'\ufffe', // 0x0B -> UNDEFINED
|
'\ufffe', // 0x0B -> UNDEFINED
|
||||||
'\ufffe', // 0x0C -> UNDEFINED
|
'\ufffe', // 0x0C -> UNDEFINED
|
||||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||||
'\u000e', // 0x0E -> SHIFT OUT
|
'\u000e', // 0x0E -> SHIFT OUT
|
||||||
'\ufffe', // 0x0F -> UNDEFINED
|
'\ufffe', // 0x0F -> UNDEFINED
|
||||||
'\ufffe', // 0x10 -> UNDEFINED
|
'\ufffe', // 0x10 -> UNDEFINED
|
||||||
@ -152,7 +152,7 @@ object PetsciiEncoding {
|
|||||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||||
'\n' , // 0x8D -> LINE FEED
|
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||||
'\u000f', // 0x8E -> SHIFT IN
|
'\u000f', // 0x8E -> SHIFT IN
|
||||||
'\ufffe', // 0x8F -> UNDEFINED
|
'\ufffe', // 0x8F -> UNDEFINED
|
||||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||||
@ -283,7 +283,7 @@ object PetsciiEncoding {
|
|||||||
'\ufffe', // 0x0A -> UNDEFINED
|
'\ufffe', // 0x0A -> UNDEFINED
|
||||||
'\ufffe', // 0x0B -> UNDEFINED
|
'\ufffe', // 0x0B -> UNDEFINED
|
||||||
'\ufffe', // 0x0C -> UNDEFINED
|
'\ufffe', // 0x0C -> UNDEFINED
|
||||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||||
'\u000e', // 0x0E -> SHIFT OUT
|
'\u000e', // 0x0E -> SHIFT OUT
|
||||||
'\ufffe', // 0x0F -> UNDEFINED
|
'\ufffe', // 0x0F -> UNDEFINED
|
||||||
'\ufffe', // 0x10 -> UNDEFINED
|
'\ufffe', // 0x10 -> UNDEFINED
|
||||||
@ -411,7 +411,7 @@ object PetsciiEncoding {
|
|||||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||||
'\n' , // 0x8D -> LINE FEED
|
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||||
'\u000f', // 0x8E -> SHIFT IN
|
'\u000f', // 0x8E -> SHIFT IN
|
||||||
'\ufffe', // 0x8F -> UNDEFINED
|
'\ufffe', // 0x8F -> UNDEFINED
|
||||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||||
@ -1061,6 +1061,7 @@ object PetsciiEncoding {
|
|||||||
'}' -> '├'
|
'}' -> '├'
|
||||||
'|' -> '│'
|
'|' -> '│'
|
||||||
'\\' -> '╲'
|
'\\' -> '╲'
|
||||||
|
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
|
||||||
else -> chr
|
else -> chr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,7 +1077,10 @@ object PetsciiEncoding {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
val case = if (lowercase) "lower" else "upper"
|
||||||
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
|
if(chr.isISOControl())
|
||||||
|
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
|
||||||
|
else
|
||||||
|
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1119,7 +1123,10 @@ object PetsciiEncoding {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
val case = if (lowercase) "lower" else "upper"
|
||||||
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
|
if(chr.isISOControl())
|
||||||
|
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
|
||||||
|
else
|
||||||
|
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.cx16
|
package prog8.code.target.cx16
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -14,9 +15,11 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
|
||||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
override val BSSHIGHRAM_END = 0xbfffu // Rom starts at $c000
|
||||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
override val BSSGOLDENRAM_START = 0x0400u
|
||||||
|
override val BSSGOLDENRAM_END = 0x07ffu
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -35,11 +38,11 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
when(selectedEmulator) {
|
when(selectedEmulator) {
|
||||||
1 -> {
|
1 -> {
|
||||||
emulator = "x16emu"
|
emulator = "x16emu"
|
||||||
extraArgs = emptyList()
|
extraArgs = listOf("-debug")
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
emulator = "box16"
|
emulator = "box16"
|
||||||
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
|
extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString()))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
||||||
@ -48,7 +51,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("\nStarting Commander X16 emulator $emulator...")
|
println("\nStarting Commander X16 emulator $emulator...")
|
||||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
|
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
|
||||||
val process: Process = processb.start()
|
val process: Process = processb.start()
|
||||||
@ -59,7 +62,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = CX16Zeropage(compilerOptions)
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
|
golden = GoldenRam(compilerOptions, 0x0400u until 0x0800u)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,16 +40,15 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
ZeropageType.DONTUSE -> {
|
ZeropageType.DONTUSE -> {
|
||||||
free.clear() // don't use zeropage at all
|
free.clear() // don't use zeropage at all
|
||||||
}
|
}
|
||||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
allocateCx16VirtualRegisters()
|
allocateCx16VirtualRegisters()
|
||||||
|
retainAllowed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +57,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
for(reg in 0..15) {
|
for(reg in 0..15) {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
57
codeCore/src/prog8/code/target/pet/PETMachineDefinition.kt
Normal file
57
codeCore/src/prog8/code/target/pet/PETMachineDefinition.kt
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package prog8.code.target.pet
|
||||||
|
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.code.target.cbm.Mflpt5
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
class PETMachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
|
override val cpu = CpuType.CPU6502
|
||||||
|
|
||||||
|
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||||
|
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||||
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
|
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||||
|
|
||||||
|
override val BSSHIGHRAM_START = 0u
|
||||||
|
override val BSSHIGHRAM_END = 0u
|
||||||
|
override val BSSGOLDENRAM_START = 0u
|
||||||
|
override val BSSGOLDENRAM_END = 0u
|
||||||
|
|
||||||
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
|
|
||||||
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
|
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
listOf("syslib")
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||||
|
if(selectedEmulator!=1) {
|
||||||
|
System.err.println("The pet target only supports the main emulator (Vice).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println("\nStarting PET emulator...")
|
||||||
|
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||||
|
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
|
||||||
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
val process=processb.start()
|
||||||
|
process.waitFor()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
|
||||||
|
|
||||||
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = PETZeropage(compilerOptions)
|
||||||
|
// there's no golden ram.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
codeCore/src/prog8/code/target/pet/PETZeropage.kt
Normal file
59
codeCore/src/prog8/code/target/pet/PETZeropage.kt
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package prog8.code.target.pet
|
||||||
|
|
||||||
|
import prog8.code.core.CompilationOptions
|
||||||
|
import prog8.code.core.InternalCompilerException
|
||||||
|
import prog8.code.core.Zeropage
|
||||||
|
import prog8.code.core.ZeropageType
|
||||||
|
|
||||||
|
|
||||||
|
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
|
||||||
|
|
||||||
|
class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
|
override val SCRATCH_B1 = 0xb3u // temp storage for a single byte
|
||||||
|
override val SCRATCH_REG = 0xb4u // temp storage for a register, must be B1+1
|
||||||
|
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
|
||||||
|
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (options.floats) {
|
||||||
|
throw InternalCompilerException("PET target doesn't yet support floating point routines")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
|
ZeropageType.FLOATSAFE,
|
||||||
|
ZeropageType.BASICSAFE,
|
||||||
|
ZeropageType.DONTUSE
|
||||||
|
))
|
||||||
|
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
|
when (options.zeropage) {
|
||||||
|
ZeropageType.FULL -> {
|
||||||
|
free.addAll(0x00u..0xffu)
|
||||||
|
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
|
||||||
|
}
|
||||||
|
ZeropageType.KERNALSAFE -> {
|
||||||
|
free.addAll(0x00u..0xffu)
|
||||||
|
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
|
||||||
|
}
|
||||||
|
ZeropageType.FLOATSAFE,
|
||||||
|
ZeropageType.BASICSAFE -> {
|
||||||
|
free.addAll(0xb3u..0xbau) // TODO more?
|
||||||
|
}
|
||||||
|
ZeropageType.DONTUSE -> {
|
||||||
|
free.clear() // don't use zeropage at all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val distinctFree = free.distinct()
|
||||||
|
free.clear()
|
||||||
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
|
removeReservedFromFreePool()
|
||||||
|
retainAllowed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() {
|
||||||
|
TODO("Not known if PET can put the virtual regs in ZP")
|
||||||
|
}
|
||||||
|
}
|
@ -12,15 +12,23 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||||
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
|
override val FLOAT_MEM_SIZE = 8 // 64-bits double
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||||
|
|
||||||
override var ESTACK_LO = 0u // not actually used
|
override val BSSHIGHRAM_START = 0u // not actually used
|
||||||
override var ESTACK_HI = 0u // not actually used
|
override val BSSHIGHRAM_END = 0u // not actually used
|
||||||
|
override val BSSGOLDENRAM_START = 0u // not actually used
|
||||||
|
override val BSSGOLDENRAM_END = 0u // not actually used
|
||||||
override lateinit var zeropage: Zeropage // not actually used
|
override lateinit var zeropage: Zeropage // not actually used
|
||||||
override lateinit var golden: GoldenRam // not actually used
|
override lateinit var golden: GoldenRam // not actually used
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number): String {
|
||||||
|
// little endian binary representation
|
||||||
|
val bits = num.toDouble().toBits().toULong()
|
||||||
|
val hexStr = bits.toString(16).padStart(16, '0')
|
||||||
|
val parts = hexStr.chunked(2).map { "\$" + it }
|
||||||
|
return parts.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return listOf("syslib")
|
return listOf("syslib")
|
||||||
@ -44,9 +52,24 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = false
|
override fun isIOAddress(address: UInt): Boolean = false
|
||||||
|
|
||||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = VirtualZeropage(compilerOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(irSource: String)
|
fun runProgram(irSource: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
|
||||||
|
override val SCRATCH_B1: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_REG: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_W1: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_W2: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id "org.jetbrains.kotlin.jvm"
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
id "io.kotest" version "0.3.9"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -25,22 +26,38 @@ compileTestKotlin {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':compilerAst')
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||||
|
|
||||||
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDir "${project.projectDir}/src"
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDir "${project.projectDir}/res"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDir "${project.projectDir}/test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: there are no unit tests in this module!
|
test {
|
||||||
|
// Enable JUnit 5 (Gradle 4.6+).
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
// Always run tests, even when nothing changed.
|
||||||
|
dependsOn 'cleanTest'
|
||||||
|
|
||||||
|
// Show test results.
|
||||||
|
testLogging {
|
||||||
|
events "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +1,49 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.StConstant
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.code.StMemVar
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.code.SymbolTable
|
||||||
import prog8.ast.statements.VarDeclType
|
|
||||||
import prog8.code.core.IMachineDefinition
|
import prog8.code.core.IMachineDefinition
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||||
|
|
||||||
|
|
||||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
|
||||||
|
|
||||||
var numberOfOptimizations = 0
|
var numberOfOptimizations = 0
|
||||||
|
|
||||||
var linesByFour = getLinesBy(lines, 4)
|
var linesByFour = getLinesBy(lines, 4)
|
||||||
|
|
||||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
var mods = optimizeIncDec(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeIncDec(linesByFour)
|
mods = optimizeStoreLoadSame(linesByFour, machine, symbolTable)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeCmpSequence(linesByFour)
|
mods = optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
mods = optimizeUselessPushPopStack(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
|
mods = optimizeUnneededTempvarInAdd(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
@ -52,14 +51,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
}
|
}
|
||||||
|
|
||||||
var linesByFourteen = getLinesBy(lines, 14)
|
var linesByFourteen = getLinesBy(lines, 14)
|
||||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program)
|
mods = optimizeSamePointerIndexing(linesByFourteen)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
@ -91,45 +90,11 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
|||||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||||
|
|
||||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
private fun optimizeSameAssignments(
|
||||||
// when statement (on bytes) generates a sequence of:
|
linesByFourteen: List<List<IndexedValue<String>>>,
|
||||||
// lda $ce01,x
|
machine: IMachineDefinition,
|
||||||
// cmp #$20
|
symbolTable: SymbolTable
|
||||||
// beq check_prog8_s72choice_32
|
): List<Modification> {
|
||||||
// lda $ce01,x
|
|
||||||
// cmp #$21
|
|
||||||
// beq check_prog8_s73choice_33
|
|
||||||
// the repeated lda can be removed
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for(lines in linesByFour) {
|
|
||||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
|
||||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
|
||||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
|
||||||
// this is a lot harder for word values because the instruction sequence varies.
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for(lines in linesByFour) {
|
|
||||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
|
||||||
lines[1].value.trim()=="dex" &&
|
|
||||||
lines[2].value.trim()=="inx" &&
|
|
||||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
|
||||||
mods.add(Modification(lines[3].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
|
||||||
|
|
||||||
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
@ -154,8 +119,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val fourthvalue = sixth.substring(4)
|
val fourthvalue = sixth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||||
val address1 = getAddressArg(first, program)
|
val address1 = getAddressArg(first, symbolTable)
|
||||||
val address2 = getAddressArg(second, program)
|
val address2 = getAddressArg(second, symbolTable)
|
||||||
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||||
mods.add(Modification(lines[4].index, true, null))
|
mods.add(Modification(lines[4].index, true, null))
|
||||||
mods.add(Modification(lines[5].index, true, null))
|
mods.add(Modification(lines[5].index, true, null))
|
||||||
@ -168,7 +133,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val secondvalue = third.substring(4)
|
val secondvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue) {
|
if(firstvalue==secondvalue) {
|
||||||
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address))
|
if(address==null || !machine.isIOAddress(address))
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
}
|
}
|
||||||
@ -251,7 +216,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
val fourthvalue = fourth.substring(4)
|
val fourthvalue = fourth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
@ -275,7 +240,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val firstvalue = first.substring(4)
|
val firstvalue = first.substring(4)
|
||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
if(firstvalue==thirdvalue) {
|
if(firstvalue==thirdvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
@ -295,7 +260,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val secondvalue = second.substring(4)
|
val secondvalue = second.substring(4)
|
||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
val reg2 = second[2]
|
val reg2 = second[2]
|
||||||
@ -308,26 +273,15 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
/*
|
/*
|
||||||
sta A ; or stz double store, remove this first one
|
sta A ; or stz double store, remove this first one
|
||||||
sta A ; or stz
|
sta A ; or stz
|
||||||
*/
|
However, this cannot be done relyably because 'A' could be a constant symbol referring to an I/O address.
|
||||||
if(!overlappingMods && first.isStoreRegOrZero() && second.isStoreRegOrZero()) {
|
We can't see that here and would otherwise delete valid double stores.
|
||||||
if(first[2]==second[2]) {
|
*/
|
||||||
val firstvalue = first.substring(4)
|
|
||||||
val secondvalue = second.substring(4)
|
|
||||||
if(firstvalue==secondvalue) {
|
|
||||||
val address = getAddressArg(first, program)
|
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
|
||||||
overlappingMods = true
|
|
||||||
mods.add(Modification(lines[0].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
|
||||||
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
// 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:
|
// if Y isn't modified in between we can omit the second LDY:
|
||||||
@ -369,7 +323,11 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
|
|||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeStoreLoadSame(
|
||||||
|
linesByFour: List<List<IndexedValue<String>>>,
|
||||||
|
machine: IMachineDefinition,
|
||||||
|
symbolTable: SymbolTable
|
||||||
|
): List<Modification> {
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (lines in linesByFour) {
|
for (lines in linesByFour) {
|
||||||
@ -397,7 +355,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// no branch instruction follows, we can remove the load instruction
|
// no branch instruction follows, we can remove the load instruction
|
||||||
val address = getAddressArg(lines[2].value, program)
|
val address = getAddressArg(lines[2].value, symbolTable)
|
||||||
address==null || !machine.isIOAddress(address)
|
address==null || !machine.isIOAddress(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,15 +381,9 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
|||||||
} else if(first=="phx" && second=="pla") {
|
} else if(first=="phx" && second=="pla") {
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
mods.add(Modification(lines[2].index, false, " txa"))
|
mods.add(Modification(lines[2].index, false, " txa"))
|
||||||
} else if(first=="phx" && second=="ply") {
|
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
|
||||||
mods.add(Modification(lines[2].index, false, " txy"))
|
|
||||||
} else if(first=="phy" && second=="pla") {
|
} else if(first=="phy" && second=="pla") {
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
mods.add(Modification(lines[2].index, false, " tya"))
|
mods.add(Modification(lines[2].index, false, " tya"))
|
||||||
} else if(first=="phy" && second=="plx") {
|
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
|
||||||
mods.add(Modification(lines[2].index, false, " tyx"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mods
|
return mods
|
||||||
@ -439,7 +391,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
|||||||
|
|
||||||
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||||
|
|
||||||
private fun getAddressArg(line: String, program: Program): UInt? {
|
private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
|
||||||
|
// try to get the constant value address, could return null if it's a symbol instead
|
||||||
val loadArg = line.trimStart().substring(3).trim()
|
val loadArg = line.trimStart().substring(3).trim()
|
||||||
return when {
|
return when {
|
||||||
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||||
@ -450,15 +403,11 @@ private fun getAddressArg(line: String, program: Program): UInt? {
|
|||||||
val identMatch = identifierRegex.find(loadArg)
|
val identMatch = identifierRegex.find(loadArg)
|
||||||
if(identMatch!=null) {
|
if(identMatch!=null) {
|
||||||
val identifier = identMatch.value
|
val identifier = identMatch.value
|
||||||
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
when (val symbol = symbolTable.flat[identifier]) {
|
||||||
if(decl!=null) {
|
is StConstant -> symbol.value.toUInt()
|
||||||
when(decl.type){
|
is StMemVar -> symbol.address
|
||||||
VarDeclType.VAR -> null
|
else -> null
|
||||||
VarDeclType.CONST,
|
|
||||||
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else null
|
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
else -> loadArg.substring(1).toUIntOrNull()
|
else -> loadArg.substring(1).toUIntOrNull()
|
||||||
@ -524,3 +473,63 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedVal
|
|||||||
}
|
}
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
|
||||||
|
fun optimize(register: Char, lines: List<IndexedValue<String>>) {
|
||||||
|
if(lines[0].value.trimStart().startsWith("ph$register")) {
|
||||||
|
if(lines[2].value.trimStart().startsWith("pl$register")) {
|
||||||
|
val second = lines[1].value.trimStart().take(6).lowercase()
|
||||||
|
if(register!in second
|
||||||
|
&& !second.startsWith("jsr")
|
||||||
|
&& !second.startsWith("pl")
|
||||||
|
&& !second.startsWith("ph")) {
|
||||||
|
mods.add(Modification(lines[0].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (lines[3].value.trimStart().startsWith("pl$register")) {
|
||||||
|
val second = lines[1].value.trimStart().take(6).lowercase()
|
||||||
|
val third = lines[2].value.trimStart().take(6).lowercase()
|
||||||
|
if(register !in second && register !in third
|
||||||
|
&& !second.startsWith("jsr") && !third.startsWith("jsr")
|
||||||
|
&& !second.startsWith("pl") && !third.startsWith("pl")
|
||||||
|
&& !second.startsWith("ph") && !third.startsWith("ph")) {
|
||||||
|
mods.add(Modification(lines[0].index, true, null))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (lines in linesByFour) {
|
||||||
|
optimize('a', lines)
|
||||||
|
optimize('x', lines)
|
||||||
|
optimize('y', lines)
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// sequence: sta P8ZP_SCRATCH_XX / lda something / clc / adc P8ZP_SCRATCH_XX
|
||||||
|
// this can be performed without the scratch variable: clc / adc something
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
|
||||||
|
for(lines in linesByFour) {
|
||||||
|
val first = lines[0].value.trimStart()
|
||||||
|
val second = lines[1].value.trimStart()
|
||||||
|
val third = lines[2].value.trimStart()
|
||||||
|
val fourth = lines[3].value.trimStart()
|
||||||
|
if(first.startsWith("sta P8ZP_SCRATCH_") && second.startsWith("lda") && third.startsWith("clc") && fourth.startsWith("adc P8ZP_SCRATCH_") ) {
|
||||||
|
if(fourth.substring(4)==first.substring(4)) {
|
||||||
|
mods.add(Modification(lines[0].index, false, " clc"))
|
||||||
|
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
@ -1,61 +1,28 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.expressions.ArrayIndexedExpression
|
import prog8.code.ast.PtAsmSub
|
||||||
import prog8.ast.expressions.BuiltinFunctionCall
|
|
||||||
import prog8.ast.expressions.Expression
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.code.core.Cx16VirtualRegisters
|
import prog8.code.core.Cx16VirtualRegisters
|
||||||
import prog8.code.core.RegisterOrPair
|
import prog8.code.core.RegisterOrPair
|
||||||
import prog8.code.core.RegisterOrStatusflag
|
|
||||||
|
|
||||||
|
|
||||||
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||||
val order = mutableListOf<Int>()
|
val order = mutableListOf<Int>()
|
||||||
// order is:
|
// order is:
|
||||||
// 1) cx16 virtual word registers,
|
// 1) cx16 virtual word registers,
|
||||||
// 2) paired CPU registers,
|
// 2) paired CPU registers,
|
||||||
// 3) single CPU registers (X last), except A,
|
// 3) single CPU registers (order Y,X,A),
|
||||||
// 4) CPU Carry status flag
|
// 4) CPU Carry status flag
|
||||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
val args = sub.parameters.withIndex()
|
||||||
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
|
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||||
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
|
|
||||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||||
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
|
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||||
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
|
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||||
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
|
|
||||||
|
|
||||||
cx16regs.forEach { order += it.index }
|
cx16regs.forEach { order += it.index }
|
||||||
pairedRegs.forEach { order += it.index }
|
pairedRegs.forEach { order += it.index }
|
||||||
regsWithoutA.forEach {
|
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||||
if(it.value.second.registerOrPair != RegisterOrPair.X)
|
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||||
order += it.index
|
|
||||||
}
|
|
||||||
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
|
||||||
rest.forEach { order += it.index }
|
rest.forEach { order += it.index }
|
||||||
regA.forEach { order += it.index }
|
|
||||||
require(order.size==sub.parameters.size)
|
require(order.size==sub.parameters.size)
|
||||||
return order
|
return order
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
|
||||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
|
||||||
fun isClobberRisk(expr: Expression): Boolean {
|
|
||||||
when (expr) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
return paramRegisters.any {
|
|
||||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BuiltinFunctionCall -> {
|
|
||||||
if (expr.name == "lsb" || expr.name == "msb")
|
|
||||||
return isClobberRisk(expr.args[0])
|
|
||||||
if (expr.name == "mkword")
|
|
||||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
|
||||||
return !expr.isSimple
|
|
||||||
}
|
|
||||||
else -> return !expr.isSimple
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args.size>1 && args.any { isClobberRisk(it) }
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import com.github.michaelbull.result.Ok
|
|
||||||
import com.github.michaelbull.result.Result
|
|
||||||
import com.github.michaelbull.result.mapError
|
|
||||||
import prog8.ast.generatedLabelPrefix
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import java.io.File
|
import prog8.code.target.C64Target
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.isRegularFile
|
|
||||||
|
|
||||||
|
|
||||||
internal class AssemblyProgram(
|
internal class AssemblyProgram(
|
||||||
@ -20,23 +14,28 @@ internal class AssemblyProgram(
|
|||||||
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
||||||
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
||||||
private val binFile = outputDir.resolve("$name.bin")
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
|
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
|
||||||
private val listFile = outputDir.resolve("$name.list")
|
private val listFile = outputDir.resolve("$name.list")
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||||
|
|
||||||
val assemblerCommand: List<String>
|
val assemblerCommand: List<String>
|
||||||
|
|
||||||
when (compTarget.name) {
|
when (compTarget.name) {
|
||||||
in setOf("c64", "c128", "cx16") -> {
|
in setOf("c64", "c128", "cx16", "pet32") -> {
|
||||||
// CBM machines .prg generation.
|
// CBM machines .prg generation.
|
||||||
|
|
||||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
"-Wall", // "-Wno-strict-bool", "-Werror",
|
||||||
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
|
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(options.warnSymbolShadowing)
|
||||||
|
command.add("-Wshadow")
|
||||||
|
else
|
||||||
|
command.add("-Wno-shadow")
|
||||||
|
|
||||||
if(options.asmQuiet)
|
if(options.asmQuiet)
|
||||||
command.add("--quiet")
|
command.add("--quiet")
|
||||||
|
|
||||||
@ -63,12 +62,17 @@ internal class AssemblyProgram(
|
|||||||
"atari" -> {
|
"atari" -> {
|
||||||
// Atari800XL .xex generation.
|
// Atari800XL .xex generation.
|
||||||
|
|
||||||
// TODO are these options okay?
|
// TODO are these options okay for atari?
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
"-Wall", // "-Werror", "-Wno-strict-bool"
|
||||||
"--no-monitor"
|
"--no-monitor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(options.warnSymbolShadowing)
|
||||||
|
command.add("-Wshadow")
|
||||||
|
else
|
||||||
|
command.add("-Wno-shadow")
|
||||||
|
|
||||||
if(options.asmQuiet)
|
if(options.asmQuiet)
|
||||||
command.add("--quiet")
|
command.add("--quiet")
|
||||||
|
|
||||||
@ -104,7 +108,7 @@ internal class AssemblyProgram(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeGeneratedLabelsFromMonlist() {
|
private fun removeGeneratedLabelsFromMonlist() {
|
||||||
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
|
||||||
val lines = viceMonListFile.toFile().readLines()
|
val lines = viceMonListFile.toFile().readLines()
|
||||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
@ -124,24 +128,9 @@ internal class AssemblyProgram(
|
|||||||
breakpoints.add("break \$" + match.groupValues[1])
|
breakpoints.add("break \$" + match.groupValues[1])
|
||||||
}
|
}
|
||||||
val num = breakpoints.size
|
val num = breakpoints.size
|
||||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
breakpoints.add(0, "; breakpoint list now follows")
|
||||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||||
breakpoints.add(2, "del")
|
breakpoints.add(2, "del")
|
||||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
|
||||||
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
|
|
||||||
return com.github.michaelbull.result.runCatching {
|
|
||||||
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
|
|
||||||
}.mapError { NoSuchFileException(File(filename)) }
|
|
||||||
} else {
|
|
||||||
val sib = Path(source.origin).resolveSibling(filename)
|
|
||||||
if (sib.isRegularFile())
|
|
||||||
Ok(SourceCode.File(sib).text)
|
|
||||||
else
|
|
||||||
Ok(SourceCode.File(Path(filename)).text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,918 +0,0 @@
|
|||||||
package prog8.codegen.cpu6502
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.code.core.*
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
internal class ExpressionsAsmGen(private val program: Program,
|
|
||||||
private val asmgen: AsmGen,
|
|
||||||
private val allocator: VariableAllocator) {
|
|
||||||
|
|
||||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
|
||||||
internal fun translateExpression(expression:Expression) {
|
|
||||||
if (this.asmgen.options.slowCodegenWarnings) {
|
|
||||||
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
|
|
||||||
}
|
|
||||||
translateExpressionInternal(expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// the rest of the methods are all PRIVATE
|
|
||||||
|
|
||||||
|
|
||||||
private fun translateExpressionInternal(expression: Expression) {
|
|
||||||
|
|
||||||
when(expression) {
|
|
||||||
is PrefixExpression -> translateExpression(expression)
|
|
||||||
is BinaryExpression -> translateExpression(expression)
|
|
||||||
is ArrayIndexedExpression -> translateExpression(expression)
|
|
||||||
is TypecastExpression -> translateExpression(expression)
|
|
||||||
is AddressOf -> translateExpression(expression)
|
|
||||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
|
||||||
is NumericLiteral -> translateExpression(expression)
|
|
||||||
is IdentifierReference -> translateExpression(expression)
|
|
||||||
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
|
|
||||||
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
|
||||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
|
||||||
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
|
||||||
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
|
|
||||||
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
|
||||||
else -> TODO("missing expression asmgen for $expression")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
|
|
||||||
// only for use in nested expression evaluation
|
|
||||||
|
|
||||||
val sub = call.target.targetSubroutine(program)!!
|
|
||||||
asmgen.saveXbeforeCall(call)
|
|
||||||
asmgen.translateFunctionCall(call, true)
|
|
||||||
if(sub.regXasResult()) {
|
|
||||||
// store the return value in X somewhere that we can access again below
|
|
||||||
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
|
||||||
}
|
|
||||||
asmgen.restoreXafterCall(call)
|
|
||||||
|
|
||||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
|
||||||
for ((_, reg) in returns) {
|
|
||||||
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
|
||||||
if (reg.registerOrPair != null) {
|
|
||||||
when (reg.registerOrPair!!) {
|
|
||||||
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
|
||||||
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
|
||||||
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
|
||||||
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
|
|
||||||
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
|
||||||
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
|
|
||||||
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
|
|
||||||
RegisterOrPair.R0,
|
|
||||||
RegisterOrPair.R1,
|
|
||||||
RegisterOrPair.R2,
|
|
||||||
RegisterOrPair.R3,
|
|
||||||
RegisterOrPair.R4,
|
|
||||||
RegisterOrPair.R5,
|
|
||||||
RegisterOrPair.R6,
|
|
||||||
RegisterOrPair.R7,
|
|
||||||
RegisterOrPair.R8,
|
|
||||||
RegisterOrPair.R9,
|
|
||||||
RegisterOrPair.R10,
|
|
||||||
RegisterOrPair.R11,
|
|
||||||
RegisterOrPair.R12,
|
|
||||||
RegisterOrPair.R13,
|
|
||||||
RegisterOrPair.R14,
|
|
||||||
RegisterOrPair.R15 -> {
|
|
||||||
asmgen.out(
|
|
||||||
"""
|
|
||||||
lda cx16.${reg.registerOrPair.toString().lowercase()}
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else when(reg.statusflag) {
|
|
||||||
Statusflag.Pc -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #0
|
|
||||||
rol a
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
}
|
|
||||||
Statusflag.Pz -> {
|
|
||||||
asmgen.out("""
|
|
||||||
beq +
|
|
||||||
lda #0
|
|
||||||
beq ++
|
|
||||||
+ lda #1
|
|
||||||
+ sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
}
|
|
||||||
Statusflag.Pv -> {
|
|
||||||
asmgen.out("""
|
|
||||||
bvs +
|
|
||||||
lda #0
|
|
||||||
beq ++
|
|
||||||
+ lda #1
|
|
||||||
+ sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
}
|
|
||||||
Statusflag.Pn -> {
|
|
||||||
asmgen.out("""
|
|
||||||
bmi +
|
|
||||||
lda #0
|
|
||||||
beq ++
|
|
||||||
+ lda #1
|
|
||||||
+ sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(typecast: TypecastExpression) {
|
|
||||||
translateExpressionInternal(typecast.expression)
|
|
||||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
|
||||||
DataType.UBYTE, DataType.BOOL -> {
|
|
||||||
when(typecast.type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
|
||||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
|
||||||
else
|
|
||||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
when(typecast.type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {}
|
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(typecast.type) {
|
|
||||||
DataType.BYTE, DataType.UBYTE -> {}
|
|
||||||
DataType.WORD, DataType.UWORD -> {}
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
when(typecast.type) {
|
|
||||||
DataType.BYTE, DataType.UBYTE -> {}
|
|
||||||
DataType.WORD, DataType.UWORD -> {}
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
when(typecast.type) {
|
|
||||||
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
|
|
||||||
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
|
|
||||||
DataType.FLOAT -> {}
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.STR -> {
|
|
||||||
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
|
|
||||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
|
||||||
}
|
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: AddressOf) {
|
|
||||||
val name = asmgen.asmVariableName(expr.identifier)
|
|
||||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: NumericLiteral) {
|
|
||||||
when(expr.type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
|
||||||
lda #<${expr.number.toHex()}
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
lda #>${expr.number.toHex()}
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
""")
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
val floatConst = allocator.getFloatAsmConst(expr.number)
|
|
||||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: IdentifierReference) {
|
|
||||||
val varname = asmgen.asmVariableName(expr)
|
|
||||||
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
|
|
||||||
}
|
|
||||||
in IterableDatatypes -> {
|
|
||||||
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("stack push weird variable type $expr")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: BinaryExpression) {
|
|
||||||
// Uses evalstack to evaluate the given expression.
|
|
||||||
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
|
||||||
val rightIDt = expr.right.inferType(program)
|
|
||||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
|
||||||
throw AssemblyError("can't infer type of both expression operands")
|
|
||||||
|
|
||||||
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
// see if we can apply some optimized routines
|
|
||||||
when(expr.operator) {
|
|
||||||
"+" -> {
|
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
|
||||||
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
|
||||||
if (leftVal!=null && leftVal in -4..4) {
|
|
||||||
translateExpressionInternal(expr.right)
|
|
||||||
if(rightDt in ByteDatatypes) {
|
|
||||||
val incdec = if(leftVal<0) "dec" else "inc"
|
|
||||||
repeat(leftVal.absoluteValue) {
|
|
||||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// word
|
|
||||||
if(leftVal<0) {
|
|
||||||
repeat(leftVal.absoluteValue) {
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
dec P8ESTACK_HI+1,x
|
|
||||||
+ dec P8ESTACK_LO+1,x""")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repeat(leftVal) {
|
|
||||||
asmgen.out("""
|
|
||||||
inc P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
inc P8ESTACK_HI+1,x
|
|
||||||
+""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else if (rightVal!=null && rightVal in -4..4)
|
|
||||||
{
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
if(leftDt in ByteDatatypes) {
|
|
||||||
val incdec = if(rightVal<0) "dec" else "inc"
|
|
||||||
repeat(rightVal.absoluteValue) {
|
|
||||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// word
|
|
||||||
if(rightVal<0) {
|
|
||||||
repeat(rightVal.absoluteValue) {
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
dec P8ESTACK_HI+1,x
|
|
||||||
+ dec P8ESTACK_LO+1,x""")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repeat(rightVal) {
|
|
||||||
asmgen.out("""
|
|
||||||
inc P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
inc P8ESTACK_HI+1,x
|
|
||||||
+""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"-" -> {
|
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
|
||||||
if (rightVal!=null && rightVal in -4..4)
|
|
||||||
{
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
if(leftDt in ByteDatatypes) {
|
|
||||||
val incdec = if(rightVal<0) "inc" else "dec"
|
|
||||||
repeat(rightVal.absoluteValue) {
|
|
||||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// word
|
|
||||||
if(rightVal>0) {
|
|
||||||
repeat(rightVal.absoluteValue) {
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
dec P8ESTACK_HI+1,x
|
|
||||||
+ dec P8ESTACK_LO+1,x""")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
repeat(rightVal) {
|
|
||||||
asmgen.out("""
|
|
||||||
inc P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
inc P8ESTACK_HI+1,x
|
|
||||||
+""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
">>" -> {
|
|
||||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
|
||||||
if(amount!=null) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
when (leftDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if (amount <= 2)
|
|
||||||
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
|
||||||
repeat(amount) { asmgen.out(" lsr a") }
|
|
||||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if (amount <= 2)
|
|
||||||
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
|
||||||
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
|
||||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if(amount>=16) {
|
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
|
||||||
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
|
||||||
else
|
|
||||||
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var left = amount
|
|
||||||
while (left >= 7) {
|
|
||||||
asmgen.out(" jsr math.shift_right_uw_7")
|
|
||||||
left -= 7
|
|
||||||
}
|
|
||||||
if (left in 0..2)
|
|
||||||
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
|
||||||
else
|
|
||||||
asmgen.out(" jsr math.shift_right_uw_$left")
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(amount>=16) {
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_HI+1,x
|
|
||||||
bmi +
|
|
||||||
lda #0
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
sta P8ESTACK_HI+1,x
|
|
||||||
beq ++
|
|
||||||
+ lda #255
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
sta P8ESTACK_HI+1,x
|
|
||||||
+""")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var left = amount
|
|
||||||
while (left >= 7) {
|
|
||||||
asmgen.out(" jsr math.shift_right_w_7")
|
|
||||||
left -= 7
|
|
||||||
}
|
|
||||||
if (left in 0..2)
|
|
||||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
|
||||||
else
|
|
||||||
asmgen.out(" jsr math.shift_right_w_$left")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"<<" -> {
|
|
||||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
|
||||||
if(amount!=null) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
if (leftDt in ByteDatatypes) {
|
|
||||||
if (amount <= 2)
|
|
||||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
|
||||||
else {
|
|
||||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
|
||||||
repeat(amount) { asmgen.out(" asl a") }
|
|
||||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var left = amount
|
|
||||||
while (left >= 7) {
|
|
||||||
asmgen.out(" jsr math.shift_left_w_7")
|
|
||||||
left -= 7
|
|
||||||
}
|
|
||||||
if (left in 0..2)
|
|
||||||
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
|
||||||
else
|
|
||||||
asmgen.out(" jsr math.shift_left_w_$left")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"*" -> {
|
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
|
||||||
val leftVar = expr.left as? IdentifierReference
|
|
||||||
val rightVar = expr.right as? IdentifierReference
|
|
||||||
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
|
|
||||||
return translateSquared(leftVar, leftDt)
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = expr.right.constValue(program)
|
|
||||||
if(value!=null) {
|
|
||||||
if(rightDt in IntegerDatatypes) {
|
|
||||||
val amount = value.number.toInt()
|
|
||||||
if(amount==2) {
|
|
||||||
// optimize x*2 common case
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
if(leftDt in ByteDatatypes) {
|
|
||||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
|
||||||
} else {
|
|
||||||
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when(rightDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if(amount in asmgen.optimizedByteMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(amount in asmgen.optimizedByteMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if(amount in asmgen.optimizedWordMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(amount in asmgen.optimizedWordMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"/" -> {
|
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
|
||||||
if(rightVal!=null && rightVal==2) {
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
when (leftDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
bpl +
|
|
||||||
inc P8ESTACK_LO+1,x
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
+ asl a
|
|
||||||
ror P8ESTACK_LO+1,x""")
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_HI+1,x
|
|
||||||
bpl ++
|
|
||||||
inc P8ESTACK_LO+1,x
|
|
||||||
bne +
|
|
||||||
inc P8ESTACK_HI+1,x
|
|
||||||
+ lda P8ESTACK_HI+1,x
|
|
||||||
+ asl a
|
|
||||||
ror P8ESTACK_HI+1,x
|
|
||||||
ror P8ESTACK_LO+1,x""")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird dt")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in ComparisonOperators -> {
|
|
||||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
|
||||||
val rightVal = expr.right.constValue(program)?.number
|
|
||||||
if(rightVal==0.0)
|
|
||||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
|
||||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
|
||||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
|
||||||
|
|
||||||
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators) {
|
|
||||||
translateCompareStrings(expr.left, expr.operator, expr.right)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
translateExpressionInternal(expr.right)
|
|
||||||
when (leftDt) {
|
|
||||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
|
||||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
|
||||||
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
|
||||||
else -> throw AssemblyError("non-numerical datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
|
||||||
if(expr.isSimple) {
|
|
||||||
if(operator=="!=") {
|
|
||||||
when (dt) {
|
|
||||||
in ByteDatatypes -> {
|
|
||||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.A, dt == DataType.BYTE)
|
|
||||||
asmgen.out("""
|
|
||||||
beq +
|
|
||||||
lda #1
|
|
||||||
+ sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in WordDatatypes -> {
|
|
||||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, dt == DataType.WORD)
|
|
||||||
asmgen.out("""
|
|
||||||
sty P8ZP_SCRATCH_B1
|
|
||||||
ora P8ZP_SCRATCH_B1
|
|
||||||
beq +
|
|
||||||
lda #1
|
|
||||||
+ sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.FAC1, true)
|
|
||||||
asmgen.out("""
|
|
||||||
jsr floats.SIGN
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex""")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* operator == is not worth it to special case, the code is mostly larger */
|
|
||||||
}
|
|
||||||
translateExpressionInternal(expr)
|
|
||||||
when(operator) {
|
|
||||||
"==" -> {
|
|
||||||
when(dt) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"!=" -> {
|
|
||||||
when(dt) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
|
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"<" -> {
|
|
||||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
|
||||||
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
|
|
||||||
when(dt) {
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
">" -> {
|
|
||||||
when(dt) {
|
|
||||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
|
|
||||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"<=" -> {
|
|
||||||
when(dt) {
|
|
||||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
|
|
||||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
">=" -> {
|
|
||||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
|
||||||
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
|
|
||||||
when(dt) {
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
|
|
||||||
else -> throw AssemblyError("wrong dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid comparison operator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
|
||||||
val asmVar = asmgen.asmVariableName(variable)
|
|
||||||
when(dt) {
|
|
||||||
DataType.BYTE, DataType.UBYTE -> {
|
|
||||||
asmgen.out(" lda $asmVar")
|
|
||||||
asmgen.signExtendAYlsb(dt)
|
|
||||||
asmgen.out(" jsr math.square")
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("require integer dt for square")
|
|
||||||
}
|
|
||||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: PrefixExpression) {
|
|
||||||
translateExpressionInternal(expr.expression)
|
|
||||||
val itype = expr.inferType(program)
|
|
||||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when(expr.operator) {
|
|
||||||
"+" -> {}
|
|
||||||
"-" -> {
|
|
||||||
when(type) {
|
|
||||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
|
||||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"~" -> {
|
|
||||||
when(type) {
|
|
||||||
in ByteDatatypes ->
|
|
||||||
asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+1,x
|
|
||||||
eor #255
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
""")
|
|
||||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
|
||||||
val elementIDt = arrayExpr.inferType(program)
|
|
||||||
if(!elementIDt.isKnown)
|
|
||||||
throw AssemblyError("unknown dt")
|
|
||||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
|
||||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
|
||||||
|
|
||||||
val arrayVarDecl = arrayExpr.arrayvar.targetVarDecl(program)!!
|
|
||||||
if(arrayVarDecl.datatype==DataType.UWORD) {
|
|
||||||
// indexing a pointer var instead of a real array or string
|
|
||||||
if(elementDt !in ByteDatatypes)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
|
||||||
if(arrayExpr.inferType(program) isnot DataType.UBYTE)
|
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
|
||||||
if(asmgen.isZpVar(arrayExpr.arrayvar)) {
|
|
||||||
asmgen.out(" lda ($arrayVarName),y")
|
|
||||||
} else {
|
|
||||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
|
||||||
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
|
||||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val constIndexNum = arrayExpr.indexer.constIndex()
|
|
||||||
if(constIndexNum!=null) {
|
|
||||||
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
|
||||||
when(elementDt) {
|
|
||||||
in ByteDatatypes -> {
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
|
||||||
}
|
|
||||||
in WordDatatypes -> {
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird element type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when(elementDt) {
|
|
||||||
in ByteDatatypes -> {
|
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
|
||||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
|
||||||
}
|
|
||||||
in WordDatatypes -> {
|
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
|
||||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
|
||||||
asmgen.out("""
|
|
||||||
ldy #>$arrayVarName
|
|
||||||
clc
|
|
||||||
adc #<$arrayVarName
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jsr floats.push_float""")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird dt")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
|
||||||
when(operator) {
|
|
||||||
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
|
||||||
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
|
||||||
"%" -> {
|
|
||||||
if(types==DataType.BYTE)
|
|
||||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
|
||||||
asmgen.out(" jsr prog8_lib.remainder_ub")
|
|
||||||
}
|
|
||||||
"+" -> asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+2,x
|
|
||||||
clc
|
|
||||||
adc P8ESTACK_LO+1,x
|
|
||||||
inx
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
""")
|
|
||||||
"-" -> asmgen.out("""
|
|
||||||
lda P8ESTACK_LO+2,x
|
|
||||||
sec
|
|
||||||
sbc P8ESTACK_LO+1,x
|
|
||||||
inx
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
""")
|
|
||||||
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
|
||||||
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
|
||||||
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
|
||||||
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
|
||||||
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
|
||||||
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
|
||||||
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
|
||||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
|
||||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
|
||||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
|
||||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
|
||||||
else -> throw AssemblyError("invalid operator $operator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
|
|
||||||
when(operator) {
|
|
||||||
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
|
||||||
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
|
||||||
"%" -> {
|
|
||||||
if(dt==DataType.WORD)
|
|
||||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
|
||||||
asmgen.out(" jsr prog8_lib.remainder_uw")
|
|
||||||
}
|
|
||||||
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
|
||||||
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
|
||||||
"<<" -> asmgen.out(" jsr math.shift_left_w")
|
|
||||||
">>" -> {
|
|
||||||
if(dt==DataType.UWORD)
|
|
||||||
asmgen.out(" jsr math.shift_right_uw")
|
|
||||||
else
|
|
||||||
asmgen.out(" jsr math.shift_right_w")
|
|
||||||
}
|
|
||||||
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
|
||||||
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
|
||||||
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
|
||||||
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
|
||||||
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
|
||||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
|
|
||||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
|
||||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
|
||||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
|
||||||
else -> throw AssemblyError("invalid operator $operator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateBinaryOperatorFloats(operator: String) {
|
|
||||||
when(operator) {
|
|
||||||
"*" -> asmgen.out(" jsr floats.mul_f")
|
|
||||||
"/" -> asmgen.out(" jsr floats.div_f")
|
|
||||||
"+" -> asmgen.out(" jsr floats.add_f")
|
|
||||||
"-" -> asmgen.out(" jsr floats.sub_f")
|
|
||||||
"<" -> asmgen.out(" jsr floats.less_f")
|
|
||||||
">" -> asmgen.out(" jsr floats.greater_f")
|
|
||||||
"<=" -> asmgen.out(" jsr floats.lesseq_f")
|
|
||||||
">=" -> asmgen.out(" jsr floats.greatereq_f")
|
|
||||||
"==" -> asmgen.out(" jsr floats.equal_f")
|
|
||||||
"!=" -> asmgen.out(" jsr floats.notequal_f")
|
|
||||||
"%", "<<", ">>", "&", "^", "|" -> throw AssemblyError("requires integer datatype")
|
|
||||||
else -> throw AssemblyError("invalid operator $operator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
|
|
||||||
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
|
|
||||||
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
|
|
||||||
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
|
||||||
when(operator) {
|
|
||||||
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
|
||||||
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
|
||||||
"<=" -> asmgen.out("""
|
|
||||||
bpl +
|
|
||||||
lda #1
|
|
||||||
bne ++
|
|
||||||
+ lda #0
|
|
||||||
+ sta P8ESTACK_LO,x""")
|
|
||||||
">=" -> asmgen.out("""
|
|
||||||
bmi +
|
|
||||||
lda #1
|
|
||||||
bne ++
|
|
||||||
+ lda #0
|
|
||||||
+ sta P8ESTACK_LO,x""")
|
|
||||||
"<" -> asmgen.out("""
|
|
||||||
bmi +
|
|
||||||
lda #0
|
|
||||||
beq ++
|
|
||||||
+ lda #1
|
|
||||||
+ sta P8ESTACK_LO,x""")
|
|
||||||
">" -> asmgen.out("""
|
|
||||||
bpl +
|
|
||||||
lda #0
|
|
||||||
beq ++
|
|
||||||
+ lda #1
|
|
||||||
+ sta P8ESTACK_LO,x""")
|
|
||||||
}
|
|
||||||
asmgen.out(" dex")
|
|
||||||
}
|
|
||||||
}
|
|
40
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
40
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
|
import prog8.code.ast.IPtSubroutine
|
||||||
|
import prog8.code.ast.PtAsmSub
|
||||||
|
import prog8.code.ast.PtSub
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
|
||||||
|
when(this) {
|
||||||
|
is PtAsmSub -> {
|
||||||
|
return returns
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
// for non-asm subroutines, determine the return registers based on the type of the return value
|
||||||
|
return if(returntype==null)
|
||||||
|
emptyList()
|
||||||
|
else {
|
||||||
|
val register = when (returntype!!) {
|
||||||
|
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||||
|
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||||
|
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
}
|
||||||
|
listOf(Pair(register, returntype!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
|
||||||
|
return when(returntype) {
|
||||||
|
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||||
|
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||||
|
null -> null
|
||||||
|
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +1,48 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import com.github.michaelbull.result.fold
|
import com.github.michaelbull.result.fold
|
||||||
import prog8.ast.Program
|
import prog8.code.StMemVar
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.code.StStaticVariable
|
||||||
import prog8.ast.expressions.RangeExpression
|
import prog8.code.ast.PtForLoop
|
||||||
import prog8.ast.statements.ForLoop
|
import prog8.code.ast.PtIdentifier
|
||||||
|
import prog8.code.ast.PtRange
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
|
internal class ForLoopsAsmGen(
|
||||||
|
private val asmgen: AsmGen6502Internal,
|
||||||
|
private val zeropage: Zeropage
|
||||||
|
) {
|
||||||
|
|
||||||
internal fun translate(stmt: ForLoop) {
|
internal fun translate(stmt: PtForLoop) {
|
||||||
val iterableDt = stmt.iterable.inferType(program)
|
val iterableDt = stmt.iterable.type
|
||||||
if(!iterableDt.isKnown)
|
|
||||||
throw AssemblyError("unknown dt")
|
|
||||||
when(stmt.iterable) {
|
when(stmt.iterable) {
|
||||||
is RangeExpression -> {
|
is PtRange -> {
|
||||||
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
|
val range = (stmt.iterable as PtRange).toConstantIntegerRange()
|
||||||
if(range==null) {
|
if(range==null) {
|
||||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
|
translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
|
||||||
} else {
|
} else {
|
||||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
translateForOverConstRange(stmt, iterableDt, range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
|
translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
val modifiedLabel = program.makeLabel("for_modified")
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
val modifiedLabel2 = program.makeLabel("for_modifiedb")
|
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
val stepsize=range.step.asConstInteger()!!
|
||||||
|
|
||||||
if(stepsize < -1) {
|
if(stepsize < -1) {
|
||||||
val limit = range.to.constValue(program)?.number
|
val limit = range.to.asConstInteger()
|
||||||
if(limit==0.0)
|
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")
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,11 +54,39 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
|
|
||||||
val incdec = if(stepsize==1) "inc" else "dec"
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||||
|
// pre-check for end already reached
|
||||||
|
if(iterableDt==DataType.ARRAY_B) {
|
||||||
|
asmgen.out(" sta $modifiedLabel+1")
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
sbc $varname
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bpl $endLabel""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc $varname
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bmi $endLabel""")
|
||||||
|
} else {
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
cmp $varname
|
||||||
|
beq +
|
||||||
|
bcs $endLabel
|
||||||
|
+""")
|
||||||
|
else
|
||||||
|
asmgen.out(" cmp $varname | bcc $endLabel")
|
||||||
|
asmgen.out(" sta $modifiedLabel+1")
|
||||||
|
}
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
$modifiedLabel cmp #0 ; modified
|
$modifiedLabel cmp #0 ; modified
|
||||||
@ -70,11 +100,39 @@ $modifiedLabel cmp #0 ; modified
|
|||||||
// bytes, step >= 2 or <= -2
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||||
|
// pre-check for end already reached
|
||||||
|
if(iterableDt==DataType.ARRAY_B) {
|
||||||
|
asmgen.out(" sta $modifiedLabel+1")
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
clc
|
||||||
|
sbc $varname
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bpl $endLabel""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sec
|
||||||
|
sbc $varname
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bmi $endLabel""")
|
||||||
|
} else {
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
cmp $varname
|
||||||
|
beq +
|
||||||
|
bcs $endLabel
|
||||||
|
+""")
|
||||||
|
else
|
||||||
|
asmgen.out(" cmp $varname | bcc $endLabel")
|
||||||
|
asmgen.out(" sta $modifiedLabel+1")
|
||||||
|
}
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(stepsize>0) {
|
if(stepsize>0) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
@ -102,14 +160,15 @@ $modifiedLabel cmp #0 ; modified
|
|||||||
// words, step 1 or -1
|
// words, step 1 or -1
|
||||||
|
|
||||||
stepsize == 1 || stepsize == -1 -> {
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvarWord(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname+1
|
lda $varname+1
|
||||||
$modifiedLabel cmp #0 ; modified
|
$modifiedLabel cmp #0 ; modified
|
||||||
@ -136,14 +195,15 @@ $modifiedLabel2 cmp #0 ; modified
|
|||||||
stepsize > 0 -> {
|
stepsize > 0 -> {
|
||||||
|
|
||||||
// (u)words, step >= 2
|
// (u)words, step >= 2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvarWord(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
|
|
||||||
if (iterableDt == DataType.ARRAY_UW) {
|
if (iterableDt == DataType.ARRAY_UW) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -184,42 +244,26 @@ $endLabel""")
|
|||||||
else -> {
|
else -> {
|
||||||
|
|
||||||
// (u)words, step <= -2
|
// (u)words, step <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvarWord(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
|
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
|
|
||||||
if(iterableDt==DataType.ARRAY_UW) {
|
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
sec
|
sec
|
||||||
sbc #<${stepsize.absoluteValue}
|
sbc #<${stepsize.absoluteValue}
|
||||||
sta $varname
|
sta $varname
|
||||||
|
tax
|
||||||
lda $varname+1
|
lda $varname+1
|
||||||
sbc #>${stepsize.absoluteValue}
|
sbc #>${stepsize.absoluteValue}
|
||||||
sta $varname+1
|
sta $varname+1
|
||||||
$modifiedLabel cmp #0 ; modified
|
txa
|
||||||
bcc $endLabel
|
|
||||||
bne $loopLabel
|
|
||||||
lda $varname
|
|
||||||
$modifiedLabel2 cmp #0 ; modified
|
|
||||||
bcs $loopLabel
|
|
||||||
$endLabel""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
lda $varname
|
|
||||||
sec
|
|
||||||
sbc #<${stepsize.absoluteValue}
|
|
||||||
sta $varname
|
|
||||||
pha
|
|
||||||
lda $varname+1
|
|
||||||
sbc #>${stepsize.absoluteValue}
|
|
||||||
sta $varname+1
|
|
||||||
pla
|
|
||||||
$modifiedLabel2 cmp #0 ; modified
|
$modifiedLabel2 cmp #0 ; modified
|
||||||
lda $varname+1
|
lda $varname+1
|
||||||
$modifiedLabel sbc #0 ; modified
|
$modifiedLabel sbc #0 ; modified
|
||||||
@ -227,7 +271,6 @@ $modifiedLabel sbc #0 ; modified
|
|||||||
eor #$80
|
eor #$80
|
||||||
+ bpl $loopLabel
|
+ bpl $loopLabel
|
||||||
$endLabel""")
|
$endLabel""")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,12 +280,67 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
// pre-check for end already reached.
|
||||||
val endLabel = program.makeLabel("for_end")
|
// 'to' is in AY, do NOT clobber this!
|
||||||
|
if(iterableDt==DataType.ARRAY_W) {
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
sta P8ZP_SCRATCH_W2 ; to
|
||||||
|
sty P8ZP_SCRATCH_W2+1 ; to
|
||||||
|
lda $fromVar
|
||||||
|
cmp P8ZP_SCRATCH_W2
|
||||||
|
lda $fromVar+1
|
||||||
|
sbc P8ZP_SCRATCH_W2+1
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bmi $endLabel
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
cmp $fromVar
|
||||||
|
tya
|
||||||
|
sbc $fromVar+1
|
||||||
|
bvc +
|
||||||
|
eor #${'$'}80
|
||||||
|
+ bmi $endLabel
|
||||||
|
lda P8ZP_SCRATCH_REG""")
|
||||||
|
} else {
|
||||||
|
if(stepsize<0)
|
||||||
|
asmgen.out("""
|
||||||
|
cpy $fromVar+1
|
||||||
|
beq +
|
||||||
|
bcc ++
|
||||||
|
bcs $endLabel
|
||||||
|
+ cmp $fromVar
|
||||||
|
bcc +
|
||||||
|
beq +
|
||||||
|
bne $endLabel
|
||||||
|
+""")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
cpy $fromVar+1
|
||||||
|
bcc $endLabel
|
||||||
|
bne +
|
||||||
|
cmp $fromVar
|
||||||
|
bcc $endLabel
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
|
||||||
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val iterableName = asmgen.asmVariableName(ident)
|
val iterableName = asmgen.asmVariableName(ident)
|
||||||
val decl = ident.targetVarDecl(program)!!
|
val symbol = asmgen.symbolTable.lookup(ident.name)
|
||||||
|
val numElements = when(symbol) {
|
||||||
|
is StStaticVariable -> symbol.length!!
|
||||||
|
is StMemVar -> symbol.length!!
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -252,8 +350,8 @@ $endLabel""")
|
|||||||
sty $loopLabel+2
|
sty $loopLabel+2
|
||||||
$loopLabel lda ${65535.toHex()} ; modified
|
$loopLabel lda ${65535.toHex()} ; modified
|
||||||
beq $endLabel
|
beq $endLabel
|
||||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
inc $loopLabel+1
|
inc $loopLabel+1
|
||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
@ -262,19 +360,18 @@ $loopLabel lda ${65535.toHex()} ; modified
|
|||||||
$endLabel""")
|
$endLabel""")
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
val length = decl.arraysize!!.constIndex()!!
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
val indexVar = program.makeLabel("for_index")
|
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
$loopLabel sty $indexVar
|
$loopLabel sty $indexVar
|
||||||
lda $iterableName,y
|
lda $iterableName,y
|
||||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(length<=255) {
|
if(numElements<=255) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $indexVar
|
ldy $indexVar
|
||||||
iny
|
iny
|
||||||
cpy #$length
|
cpy #$numElements
|
||||||
beq $endLabel
|
beq $endLabel
|
||||||
bne $loopLabel""")
|
bne $loopLabel""")
|
||||||
} else {
|
} else {
|
||||||
@ -285,11 +382,11 @@ $loopLabel sty $indexVar
|
|||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
}
|
}
|
||||||
if(length>=16) {
|
if(numElements>=16) {
|
||||||
// allocate index var on ZP if possible
|
// allocate index var on ZP if possible
|
||||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||||
result.fold(
|
result.fold(
|
||||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||||
failure = { asmgen.out("$indexVar .byte 0") }
|
failure = { asmgen.out("$indexVar .byte 0") }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -298,9 +395,9 @@ $loopLabel sty $indexVar
|
|||||||
asmgen.out(endLabel)
|
asmgen.out(endLabel)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
val length = decl.arraysize!!.constIndex()!! * 2
|
val length = numElements * 2
|
||||||
val indexVar = program.makeLabel("for_index")
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
|
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
$loopLabel sty $indexVar
|
$loopLabel sty $indexVar
|
||||||
@ -308,7 +405,7 @@ $loopLabel sty $indexVar
|
|||||||
sta $loopvarName
|
sta $loopvarName
|
||||||
lda $iterableName+1,y
|
lda $iterableName+1,y
|
||||||
sta $loopvarName+1""")
|
sta $loopvarName+1""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(length<=127) {
|
if(length<=127) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $indexVar
|
ldy $indexVar
|
||||||
@ -328,9 +425,47 @@ $loopLabel sty $indexVar
|
|||||||
}
|
}
|
||||||
if(length>=16) {
|
if(length>=16) {
|
||||||
// allocate index var on ZP if possible
|
// allocate index var on ZP if possible
|
||||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||||
result.fold(
|
result.fold(
|
||||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
success = { (address,_,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||||
|
failure = { asmgen.out("$indexVar .byte 0") }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
asmgen.out("$indexVar .byte 0")
|
||||||
|
}
|
||||||
|
asmgen.out(endLabel)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> {
|
||||||
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
|
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #0
|
||||||
|
$loopLabel sty $indexVar
|
||||||
|
lda ${iterableName}_lsb,y
|
||||||
|
sta $loopvarName
|
||||||
|
lda ${iterableName}_msb,y
|
||||||
|
sta $loopvarName+1""")
|
||||||
|
asmgen.translate(stmt.statements)
|
||||||
|
if(numElements<=255) {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
cpy #$numElements
|
||||||
|
beq $endLabel
|
||||||
|
bne $loopLabel""")
|
||||||
|
} else {
|
||||||
|
// length is 256
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $indexVar
|
||||||
|
iny
|
||||||
|
bne $loopLabel
|
||||||
|
beq $endLabel""")
|
||||||
|
}
|
||||||
|
if(numElements>=16) {
|
||||||
|
// allocate index var on ZP if possible
|
||||||
|
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||||
|
result.fold(
|
||||||
|
success = { (address,_,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||||
failure = { asmgen.out("$indexVar .byte 0") }
|
failure = { asmgen.out("$indexVar .byte 0") }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -346,7 +481,7 @@ $loopLabel sty $indexVar
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
|
||||||
if (range.isEmpty() || range.step==0)
|
if (range.isEmpty() || range.step==0)
|
||||||
throw AssemblyError("empty range or step 0")
|
throw AssemblyError("empty range or step 0")
|
||||||
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
||||||
@ -359,18 +494,18 @@ $loopLabel sty $indexVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not one of the easy cases, generate more complex code...
|
// not one of the easy cases, generate more complex code...
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
when (range.step) {
|
when (range.step) {
|
||||||
0, 1, -1 -> {
|
0, 1, -1 -> {
|
||||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
@ -430,7 +565,7 @@ $loopLabel""")
|
|||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
// loop over word range via loopvar, step >= 2 or <= -2
|
// loop over word range via loopvar, step >= 2 or <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
when (range.step) {
|
when (range.step) {
|
||||||
0, 1, -1 -> {
|
0, 1, -1 -> {
|
||||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
@ -444,7 +579,7 @@ $loopLabel""")
|
|||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -470,16 +605,16 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if (range.last == 255) {
|
if (range.last == 255) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
inc $varname
|
inc $varname
|
||||||
@ -496,16 +631,16 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
when (range.last) {
|
when (range.last) {
|
||||||
0 -> {
|
0 -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -533,18 +668,18 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${range.first}
|
lda #<${range.first}
|
||||||
ldy #>${range.first}
|
ldy #>${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -560,18 +695,18 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${range.first}
|
lda #<${range.first}
|
||||||
ldy #>${range.first}
|
ldy #>${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -588,10 +723,9 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
|
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||||
asmgen.assignExpressionToVariable(
|
asmgen.assignExpressionToVariable(
|
||||||
range.from,
|
range.from,
|
||||||
asmgen.asmVariableName(stmt.loopVar),
|
asmgen.asmVariableName(stmt.variable),
|
||||||
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
|
stmt.variable.type)
|
||||||
stmt.definingSubroutine)
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.code.ast.*
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import prog8.ast.expressions.Expression
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.NumericLiteral
|
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
|
||||||
import prog8.ast.statements.InlineAssembly
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.SubroutineParameter
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||||
@ -18,68 +8,42 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
|||||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||||
|
|
||||||
|
|
||||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||||
|
|
||||||
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
|
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||||
saveXbeforeCall(stmt)
|
translateFunctionCall(stmt)
|
||||||
translateFunctionCall(stmt, false)
|
|
||||||
restoreXafterCall(stmt)
|
|
||||||
// just ignore any result values from the function call.
|
// just ignore any result values from the function call.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun saveXbeforeCall(stmt: IFunctionCall) {
|
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
|
||||||
if(sub.shouldSaveX()) {
|
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
|
||||||
if(regSaveOnStack)
|
|
||||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
|
||||||
else
|
|
||||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
|
||||||
if(sub.shouldSaveX()) {
|
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
|
||||||
if(regSaveOnStack)
|
|
||||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
|
||||||
else
|
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
|
||||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||||
|
|
||||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
|
internal fun translateFunctionCall(call: PtFunctionCall) {
|
||||||
// Output only the code to set up the parameters and perform the actual call
|
// Output only the code to set up the parameters and perform the actual call
|
||||||
// NOTE: does NOT output the code to deal with the result values!
|
// NOTE: does NOT output the code to deal with the result values!
|
||||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
// 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)
|
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||||
|
|
||||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
val sub = symbol?.astNode as IPtSubroutine
|
||||||
|
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub is PtAsmSub) {
|
||||||
argumentsViaRegisters(sub, call)
|
argumentsViaRegisters(sub, call)
|
||||||
if (sub.inline && asmgen.options.optimize) {
|
if (sub.inline) {
|
||||||
// inline the subroutine.
|
// inline the subroutine. (regardless of optimization settings!)
|
||||||
// we do this by copying the subroutine's statements at the call site.
|
// we do this by copying the subroutine's statements at the call site.
|
||||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||||
// (this condition has been enforced by an ast check earlier)
|
// (this condition has been enforced by an ast check earlier)
|
||||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||||
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
|
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
|
||||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.out(" jsr $subAsmName")
|
asmgen.out(" jsr $subAsmName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if(sub is PtSub) {
|
||||||
if(sub.inline)
|
|
||||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
|
||||||
|
|
||||||
if(optimizeIntArgsViaRegisters(sub)) {
|
if(optimizeIntArgsViaRegisters(sub)) {
|
||||||
if(sub.parameters.size==1) {
|
if(sub.parameters.size==1) {
|
||||||
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
||||||
@ -100,85 +64,106 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
asmgen.out(" jsr $subAsmName")
|
asmgen.out(" jsr $subAsmName")
|
||||||
}
|
}
|
||||||
|
else throw AssemblyError("invalid sub type")
|
||||||
|
|
||||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
|
||||||
|
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||||
|
return when(arg) {
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
if (arg.name == "lsb" || arg.name == "msb")
|
||||||
|
return usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||||
|
if (arg.name == "mkword")
|
||||||
|
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||||
|
return !arg.isSimple()
|
||||||
|
}
|
||||||
|
is PtAddressOf -> false
|
||||||
|
is PtIdentifier -> false
|
||||||
|
is PtMachineRegister -> false
|
||||||
|
is PtMemoryByte -> false
|
||||||
|
is PtNumber -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||||
|
val registersUsed = mutableListOf<RegisterOrStatusflag>();
|
||||||
|
|
||||||
|
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
|
||||||
|
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
|
||||||
|
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
|
||||||
|
|
||||||
if(sub.parameters.size==1) {
|
if(sub.parameters.size==1) {
|
||||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
|
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||||
} else {
|
} else {
|
||||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
|
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||||
registerArgsViaCpuStackEvaluation(call, sub)
|
optimalEvalOrder.forEach {
|
||||||
} else {
|
val param = sub.parameters[it]
|
||||||
asmsub6502ArgsEvalOrder(sub).forEach {
|
val arg = call.args[it]
|
||||||
val param = sub.parameters[it]
|
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
|
||||||
val arg = call.args[it]
|
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
|
||||||
argumentViaRegister(sub, IndexedValue(it, param), arg)
|
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||||
|
else if(registersUsed.any {it.statusflag!=null}) {
|
||||||
|
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
|
||||||
|
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||||
|
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
|
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||||
|
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||||
|
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
|
||||||
|
used
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) {
|
private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
|
||||||
// this is called when one or more of the arguments are 'complex' and
|
|
||||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
|
||||||
|
|
||||||
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
|
|
||||||
if(callee.parameters.isEmpty())
|
|
||||||
return
|
|
||||||
|
|
||||||
// use the cpu hardware stack as intermediate storage for the arguments.
|
|
||||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
|
||||||
argOrder.reversed().forEach {
|
|
||||||
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
|
|
||||||
}
|
|
||||||
argOrder.forEach {
|
|
||||||
val param = callee.parameters[it]
|
|
||||||
val targetVar = callee.searchParameter(param.name)!!
|
|
||||||
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
|
|
||||||
// pass parameter via a regular variable (not via registers)
|
// pass parameter via a regular variable (not via registers)
|
||||||
val valueIDt = value.inferType(program)
|
if(!isArgumentTypeCompatible(value.type, parameter.type))
|
||||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.type))
|
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
|
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||||
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
|
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
|
||||||
// pass argument via a register parameter
|
// pass argument via a register parameter
|
||||||
val valueIDt = value.inferType(program)
|
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
|
val paramRegister: RegisterOrStatusflag = when(sub) {
|
||||||
|
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
|
||||||
|
is PtSub -> RegisterOrStatusflag(registerOverride!!, null)
|
||||||
|
}
|
||||||
val statusflag = paramRegister.statusflag
|
val statusflag = paramRegister.statusflag
|
||||||
val register = paramRegister.registerOrPair
|
val register = paramRegister.registerOrPair
|
||||||
val requiredDt = parameter.value.type
|
val requiredDt = parameter.value.type
|
||||||
if(requiredDt!=valueDt) {
|
if(requiredDt!=value.type) {
|
||||||
if(valueDt largerThan requiredDt)
|
if(value.type largerThan requiredDt)
|
||||||
throw AssemblyError("can only convert byte values to word param types")
|
throw AssemblyError("can only convert byte values to word param types")
|
||||||
}
|
}
|
||||||
if (statusflag!=null) {
|
if (statusflag!=null) {
|
||||||
if(requiredDt!=valueDt)
|
if(requiredDt!=value.type)
|
||||||
throw AssemblyError("for statusflag, byte value is required")
|
throw AssemblyError("for statusflag, byte value is required")
|
||||||
if (statusflag == Statusflag.Pc) {
|
if (statusflag == Statusflag.Pc) {
|
||||||
// this param needs to be set last, right before the jsr
|
// this boolean param needs to be set last, right before the jsr
|
||||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||||
when(value) {
|
when(value) {
|
||||||
is NumericLiteral -> {
|
is PtNumber -> {
|
||||||
val carrySet = value.number.toInt() != 0
|
val carrySet = value.number.toInt() != 0
|
||||||
asmgen.out(if(carrySet) " sec" else " clc")
|
asmgen.out(if(carrySet) " sec" else " clc")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val sourceName = asmgen.asmVariableName(value)
|
val sourceName = asmgen.asmVariableName(value)
|
||||||
|
// note: cannot use X register here to store A because it might be used for other arguments
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
pha
|
pha
|
||||||
clc
|
clc
|
||||||
@ -189,35 +174,34 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.out("""
|
asmgen.out(" ror a")
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+""")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
return RegisterOrStatusflag(null, statusflag)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// via register or register pair
|
// via register or register pair
|
||||||
register!!
|
register!!
|
||||||
if(requiredDt largerThan valueDt) {
|
if(requiredDt largerThan value.type) {
|
||||||
// we need to sign extend the source, do this via temporary word variable
|
// we need to sign extend the source, do this via temporary word variable
|
||||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
|
||||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
|
||||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
|
||||||
} else {
|
} else {
|
||||||
|
val scope = value.definingISub()
|
||||||
val target: AsmAssignTarget =
|
val target: AsmAssignTarget =
|
||||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, sub, register = register)
|
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
|
||||||
else {
|
else {
|
||||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||||
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
|
AsmAssignTarget.fromRegisters(register, signed, value.position, scope, asmgen)
|
||||||
}
|
}
|
||||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
val src = if(value.type in PassByReferenceDatatypes) {
|
||||||
if(value is IdentifierReference) {
|
if(value is PtIdentifier) {
|
||||||
val addr = AddressOf(value, Position.DUMMY)
|
val addr = PtAddressOf(Position.DUMMY)
|
||||||
|
addr.add(value)
|
||||||
|
addr.parent = sub as PtNode
|
||||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||||
} else {
|
} else {
|
||||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
@ -225,8 +209,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
} else {
|
} else {
|
||||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
}
|
}
|
||||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
|
||||||
}
|
}
|
||||||
|
return RegisterOrStatusflag(register, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,3 +233,5 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.PtIdentifier
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.code.ast.PtNumber
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.code.ast.PtPostIncrDecr
|
||||||
import prog8.ast.statements.PostIncrDecr
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||||
internal fun translate(stmt: PostIncrDecr) {
|
internal fun translate(stmt: PtPostIncrDecr) {
|
||||||
val incr = stmt.operator=="++"
|
val incr = stmt.operator=="++"
|
||||||
val targetIdent = stmt.target.identifier
|
val targetIdent = stmt.target.identifier
|
||||||
val targetMemory = stmt.target.memoryAddress
|
val targetMemory = stmt.target.memory
|
||||||
val targetArrayIdx = stmt.target.arrayindexed
|
val targetArrayIdx = stmt.target.array
|
||||||
val scope = stmt.definingSubroutine
|
|
||||||
when {
|
when {
|
||||||
targetIdent!=null -> {
|
targetIdent!=null -> {
|
||||||
val what = asmgen.asmVariableName(targetIdent)
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
when (stmt.target.type) {
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -38,12 +37,12 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetMemory!=null -> {
|
targetMemory!=null -> {
|
||||||
when (val addressExpr = targetMemory.addressExpression) {
|
when (val addressExpr = targetMemory.address) {
|
||||||
is NumericLiteral -> {
|
is PtNumber -> {
|
||||||
val what = addressExpr.number.toHex()
|
val what = addressExpr.number.toHex()
|
||||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val what = asmgen.asmVariableName(addressExpr)
|
val what = asmgen.asmVariableName(addressExpr)
|
||||||
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -62,23 +61,62 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetArrayIdx!=null -> {
|
targetArrayIdx!=null -> {
|
||||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
|
||||||
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
val elementDt = targetArrayIdx.type
|
||||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
val constIndex = targetArrayIdx.index.asConstInteger()
|
||||||
|
if(targetArrayIdx.splitWords) {
|
||||||
|
if(constIndex!=null) {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc ${asmArrayvarname}_lsb+$constIndex | bne + | inc ${asmArrayvarname}_msb+$constIndex |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${asmArrayvarname}_lsb+$constIndex
|
||||||
|
bne +
|
||||||
|
dec ${asmArrayvarname}_msb+$constIndex
|
||||||
|
+ dec ${asmArrayvarname}_lsb+$constIndex""")
|
||||||
|
} else {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc ${asmArrayvarname}_lsb,x | bne + | inc ${asmArrayvarname}_msb,x |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${asmArrayvarname}_lsb,x
|
||||||
|
bne +
|
||||||
|
dec ${asmArrayvarname}_msb,x
|
||||||
|
+ dec ${asmArrayvarname}_lsb,x""")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if(constIndex!=null) {
|
if(constIndex!=null) {
|
||||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||||
when(elementDt) {
|
when(elementDt) {
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
in ByteDatatypes -> {
|
||||||
|
if(targetArrayIdx.usesPointerVariable) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $asmArrayvarname
|
||||||
|
clc
|
||||||
|
adc #$indexValue
|
||||||
|
sta (+) +1
|
||||||
|
lda $asmArrayvarname+1
|
||||||
|
adc #0
|
||||||
|
sta (+) +2""")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
|
} else {
|
||||||
|
asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||||
|
}
|
||||||
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||||
else
|
else
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $asmArrayvarname+$indexValue
|
lda $asmArrayvarname+$indexValue
|
||||||
bne +
|
bne +
|
||||||
dec $asmArrayvarname+$indexValue+1
|
dec $asmArrayvarname+$indexValue+1
|
||||||
+ dec $asmArrayvarname+$indexValue
|
+ dec $asmArrayvarname+$indexValue""")
|
||||||
""")
|
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
|
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
|
||||||
@ -89,36 +127,47 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, CpuRegister.X)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
|
|
||||||
asmgen.out(" tax")
|
|
||||||
when(elementDt) {
|
when(elementDt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
if(targetArrayIdx.usesPointerVariable) {
|
||||||
|
asmgen.out("""
|
||||||
|
txa
|
||||||
|
clc
|
||||||
|
adc $asmArrayvarname
|
||||||
|
sta (+) +1
|
||||||
|
lda $asmArrayvarname+1
|
||||||
|
adc #0
|
||||||
|
sta (+) +2""")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
|
} else {
|
||||||
|
asmgen.out(if (incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||||
else
|
else
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $asmArrayvarname,x
|
lda $asmArrayvarname,x
|
||||||
bne +
|
bne +
|
||||||
dec $asmArrayvarname+1,x
|
dec $asmArrayvarname+1,x
|
||||||
+ dec $asmArrayvarname,x
|
+ dec $asmArrayvarname,x""")
|
||||||
""")
|
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy #>$asmArrayvarname
|
ldy #>$asmArrayvarname
|
||||||
clc
|
clc
|
||||||
adc #<$asmArrayvarname
|
adc #<$asmArrayvarname
|
||||||
bcc +
|
bcc +
|
||||||
iny
|
iny
|
||||||
+ jsr floats.inc_var_f""")
|
+ jsr floats.inc_var_f""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird array elt dt")
|
else -> throw AssemblyError("weird array elt dt")
|
||||||
}
|
}
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
import prog8.code.*
|
import prog8.code.*
|
||||||
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||||
import prog8.compiler.CallGraph
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@ -20,30 +18,27 @@ import kotlin.math.absoluteValue
|
|||||||
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
||||||
*/
|
*/
|
||||||
internal class ProgramAndVarsGen(
|
internal class ProgramAndVarsGen(
|
||||||
val program: Program,
|
val program: PtProgram,
|
||||||
val options: CompilationOptions,
|
val options: CompilationOptions,
|
||||||
val errors: IErrorReporter,
|
val errors: IErrorReporter,
|
||||||
private val symboltable: SymbolTable,
|
private val symboltable: SymbolTable,
|
||||||
private val functioncallAsmGen: FunctionCallAsmGen,
|
private val functioncallAsmGen: FunctionCallAsmGen,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
private val allocator: VariableAllocator,
|
private val allocator: VariableAllocator,
|
||||||
private val zeropage: Zeropage
|
private val zeropage: Zeropage
|
||||||
) {
|
) {
|
||||||
private val compTarget = options.compTarget
|
private val compTarget = options.compTarget
|
||||||
private val callGraph = CallGraph(program, true)
|
private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }
|
||||||
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
|
|
||||||
|
|
||||||
internal fun generate() {
|
internal fun generate() {
|
||||||
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
|
|
||||||
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
|
|
||||||
|
|
||||||
header()
|
header()
|
||||||
val allBlocks = program.allBlocks
|
val allBlocks = program.allBlocks()
|
||||||
if(allBlocks.first().name != "main")
|
|
||||||
throw AssemblyError("first block should be 'main'")
|
if(allBlocks.first().name != "p8b_main" && allBlocks.first().name != "main")
|
||||||
|
throw AssemblyError("first block should be 'main' or 'p8b_main'")
|
||||||
|
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
program.allBlocks.forEach { block2asm(it) }
|
program.allBlocks().forEach { block2asm(it) }
|
||||||
|
|
||||||
// the global list of all floating point constants for the whole program
|
// the global list of all floating point constants for the whole program
|
||||||
asmgen.out("; global float constants")
|
asmgen.out("; global float constants")
|
||||||
@ -71,7 +66,7 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
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("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
|
asmgen.out(".cpu '$cpu'\n.enc 'none'")
|
||||||
|
|
||||||
// the global prog8 variables needed
|
// the global prog8 variables needed
|
||||||
val zp = zeropage
|
val zp = zeropage
|
||||||
@ -80,8 +75,6 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
|
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
|
||||||
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
|
|
||||||
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
|
|
||||||
asmgen.out(".endweak")
|
asmgen.out(".endweak")
|
||||||
|
|
||||||
if(options.symbolDefs.isNotEmpty()) {
|
if(options.symbolDefs.isNotEmpty()) {
|
||||||
@ -94,13 +87,13 @@ internal class ProgramAndVarsGen(
|
|||||||
when(options.output) {
|
when(options.output) {
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
asmgen.out("; ---- raw assembler program ----")
|
asmgen.out("; ---- raw assembler program ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
}
|
}
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
when(options.launcher) {
|
when(options.launcher) {
|
||||||
CbmPrgLauncherType.BASIC -> {
|
CbmPrgLauncherType.BASIC -> {
|
||||||
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
|
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
|
||||||
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
|
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
|
||||||
}
|
}
|
||||||
asmgen.out("; ---- basic program with sys call ----")
|
asmgen.out("; ---- basic program with sys call ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
@ -108,26 +101,26 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out(" .word (+), $year")
|
asmgen.out(" .word (+), $year")
|
||||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||||
asmgen.out("+\t.word 0")
|
asmgen.out("+\t.word 0")
|
||||||
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
|
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr sys.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr sys.init_system_phase2")
|
||||||
}
|
}
|
||||||
CbmPrgLauncherType.NONE -> {
|
CbmPrgLauncherType.NONE -> {
|
||||||
asmgen.out("; ---- program without basic sys call ----")
|
asmgen.out("; ---- program without basic sys call ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr sys.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr sys.init_system_phase2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutputType.XEX -> {
|
OutputType.XEX -> {
|
||||||
asmgen.out("; ---- atari xex program ----")
|
asmgen.out("; ---- atari xex program ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr sys.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr sys.init_system_phase2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,90 +133,161 @@ internal class ProgramAndVarsGen(
|
|||||||
pha""")
|
pha""")
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
|
|
||||||
when(compTarget.name) {
|
when(compTarget.name) {
|
||||||
"cx16" -> {
|
"cx16" -> {
|
||||||
if(options.floats)
|
if(options.floats)
|
||||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||||
asmgen.out(" jsr main.start")
|
asmgen.out(" jsr p8b_main.p8s_start")
|
||||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||||
}
|
}
|
||||||
"c64" -> {
|
"c64" -> {
|
||||||
asmgen.out(" jsr main.start | lda #31 | sta $01")
|
asmgen.out(" jsr p8b_main.p8s_start | lda #31 | sta $01")
|
||||||
if(!options.noSysInit)
|
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
|
||||||
else
|
|
||||||
asmgen.out(" rts")
|
|
||||||
}
|
}
|
||||||
"c128" -> {
|
"c128" -> {
|
||||||
asmgen.out(" jsr main.start")
|
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
|
||||||
// TODO c128: how to bank basic+kernal back in?
|
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||||
if(!options.noSysInit)
|
|
||||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
|
||||||
else
|
|
||||||
asmgen.out(" rts")
|
|
||||||
}
|
}
|
||||||
else -> asmgen.jmp("main.start")
|
else -> asmgen.jmp("p8b_main.p8s_start")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun memorySlabs() {
|
private fun memorySlabs() {
|
||||||
asmgen.out("; memory slabs")
|
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||||
asmgen.out("prog8_slabs\t.block")
|
asmgen.out("; memory slabs\n .section slabs_BSS")
|
||||||
for(slab in symboltable.allMemorySlabs) {
|
asmgen.out("prog8_slabs\t.block")
|
||||||
if(slab.align>1u)
|
for (slab in symboltable.allMemorySlabs) {
|
||||||
asmgen.out("\t.align ${slab.align.toHex()}")
|
if (slab.align > 1u)
|
||||||
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||||
|
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
||||||
|
}
|
||||||
|
asmgen.out("\t.bend\n .send slabs_BSS")
|
||||||
}
|
}
|
||||||
asmgen.out("\t.bend")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun footer() {
|
private fun footer() {
|
||||||
// program end
|
var relocateBssVars = false
|
||||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
var relocateBssSlabs = false
|
||||||
|
var relocatedBssStart = 0u
|
||||||
|
var relocatedBssEnd = 0u
|
||||||
|
|
||||||
|
if(options.varsGolden) {
|
||||||
|
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
|
||||||
|
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
|
||||||
|
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
|
||||||
|
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||||
|
}
|
||||||
|
relocateBssVars = true
|
||||||
|
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
|
||||||
|
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
|
||||||
|
}
|
||||||
|
else if(options.varsHighBank!=null) {
|
||||||
|
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
|
||||||
|
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
|
||||||
|
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
|
||||||
|
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||||
|
}
|
||||||
|
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||||
|
throw AssemblyError("slabs and vars high bank must be the same")
|
||||||
|
relocateBssVars = true
|
||||||
|
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
|
||||||
|
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.slabsGolden) {
|
||||||
|
if(options.compTarget.machine.BSSGOLDENRAM_START == 0u ||
|
||||||
|
options.compTarget.machine.BSSGOLDENRAM_END == 0u ||
|
||||||
|
options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) {
|
||||||
|
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
|
||||||
|
}
|
||||||
|
relocateBssSlabs = true
|
||||||
|
relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START
|
||||||
|
relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END
|
||||||
|
}
|
||||||
|
else if(options.slabsHighBank!=null) {
|
||||||
|
if(options.compTarget.machine.BSSHIGHRAM_START == 0u ||
|
||||||
|
options.compTarget.machine.BSSHIGHRAM_END == 0u ||
|
||||||
|
options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) {
|
||||||
|
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
|
||||||
|
}
|
||||||
|
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
|
||||||
|
throw AssemblyError("slabs and vars high bank must be the same")
|
||||||
|
relocateBssSlabs = true
|
||||||
|
relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START
|
||||||
|
relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.out("; bss sections")
|
||||||
|
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
|
||||||
|
if(relocateBssVars) {
|
||||||
|
if(!relocateBssSlabs)
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||||
|
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||||
|
asmgen.out("prog8_bss_section_start")
|
||||||
|
asmgen.out(" .dsection BSS")
|
||||||
|
if(relocateBssSlabs)
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
|
||||||
|
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||||
|
} else {
|
||||||
|
asmgen.out("prog8_bss_section_start")
|
||||||
|
asmgen.out(" .dsection BSS")
|
||||||
|
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||||
|
if(!relocateBssSlabs)
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||||
|
if(relocateBssSlabs) {
|
||||||
|
asmgen.out(" * = ${relocatedBssStart.toHex()}")
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun block2asm(block: Block) {
|
private fun block2asm(block: PtBlock) {
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; ---- block: '${block.name}' ----")
|
asmgen.out("; ---- block: '${block.name}' ----")
|
||||||
if(block.address!=null)
|
if(block.options.address!=null)
|
||||||
asmgen.out("* = ${block.address!!.toHex()}")
|
asmgen.out("* = ${block.options.address!!.toHex()}")
|
||||||
else {
|
else {
|
||||||
if("align_word" in block.options())
|
if(block.options.alignment==PtBlock.BlockAlignment.WORD)
|
||||||
asmgen.out("\t.align 2")
|
asmgen.out("\t.align 2")
|
||||||
else if("align_page" in block.options())
|
else if(block.options.alignment==PtBlock.BlockAlignment.PAGE)
|
||||||
asmgen.out("\t.align $100")
|
asmgen.out("\t.align $100")
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc"))
|
||||||
asmgen.outputSourceLine(block)
|
asmgen.outputSourceLine(block)
|
||||||
|
|
||||||
createBlockVariables(block)
|
createBlockVariables(block)
|
||||||
asmsubs2asm(block.statements)
|
asmsubs2asm(block.children)
|
||||||
|
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
|
|
||||||
val initializers = blockVariableInitializers.getValue(block)
|
val initializers = blockVariableInitializers.getValue(block)
|
||||||
val notInitializers = block.statements.filterNot { it in initializers }
|
val notInitializers = block.children.filterNot { it in initializers }
|
||||||
notInitializers.forEach { asmgen.translate(it) }
|
notInitializers.forEach { asmgen.translate(it) }
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
// generate subroutine to initialize block-level (global) variables
|
||||||
// generate subroutine to initialize block-level (global) variables
|
if (initializers.isNotEmpty()) {
|
||||||
if (initializers.isNotEmpty()) {
|
asmgen.out("prog8_init_vars\t.block")
|
||||||
asmgen.out("prog8_init_vars\t.block\n")
|
initializers.forEach { assign ->
|
||||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
|
||||||
asmgen.out(" rts\n .bend")
|
asmgen.translate(assign)
|
||||||
|
// the other variables that should be set to zero are done so as part of the BSS section.
|
||||||
}
|
}
|
||||||
|
asmgen.out(" rts\n .bend")
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
asmgen.out(if(block.options.forceOutput) "\n\t.bend" else "\n\t.pend")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVars(scope: StNode): Map<String, StNode> =
|
private fun getVars(scope: StNode): Map<String, StNode> =
|
||||||
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
|
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
|
||||||
|
|
||||||
private fun createBlockVariables(block: Block) {
|
private fun createBlockVariables(block: PtBlock) {
|
||||||
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") }
|
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
|
||||||
require(scope.type==StNodeType.BLOCK)
|
require(scope.type==StNodeType.BLOCK)
|
||||||
val varsInBlock = getVars(scope)
|
val varsInBlock = getVars(scope)
|
||||||
|
|
||||||
@ -247,26 +311,16 @@ internal class ProgramAndVarsGen(
|
|||||||
nonZpVariables2asm(variables)
|
nonZpVariables2asm(variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateSubroutine(sub: Subroutine) {
|
internal fun translateAsmSubroutine(sub: PtAsmSub) {
|
||||||
var onlyVariables = false
|
|
||||||
|
|
||||||
if(sub.inline) {
|
if(sub.inline) {
|
||||||
if(options.optimize) {
|
return // subroutine gets inlined at call site.
|
||||||
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
|
||||||
return
|
|
||||||
|
|
||||||
// from an inlined subroutine only the local variables are generated,
|
|
||||||
// all other code statements are omitted in the subroutine itself
|
|
||||||
// (they've been inlined at the call site, remember?)
|
|
||||||
onlyVariables = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
|
|
||||||
val asmStartScope: String
|
val asmStartScope: String
|
||||||
val asmEndScope: String
|
val asmEndScope: String
|
||||||
if(sub.definingBlock.options().contains("force_output")) {
|
if(sub.definingBlock()!!.options.forceOutput) {
|
||||||
asmStartScope = ".block"
|
asmStartScope = ".block"
|
||||||
asmEndScope = ".bend"
|
asmEndScope = ".bend"
|
||||||
} else {
|
} else {
|
||||||
@ -274,105 +328,126 @@ internal class ProgramAndVarsGen(
|
|||||||
asmEndScope = ".pend"
|
asmEndScope = ".pend"
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub.address!=null)
|
||||||
if(sub.asmAddress!=null)
|
return // already done at the memvars section
|
||||||
return // already done at the memvars section
|
|
||||||
|
|
||||||
// asmsub with most likely just an inline asm in it
|
// asmsub with most likely just an inline asm in it
|
||||||
asmgen.out("${sub.name}\t$asmStartScope")
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
sub.statements.forEach { asmgen.translate(it) }
|
sub.children.forEach { asmgen.translate(it) }
|
||||||
asmgen.out(" $asmEndScope\n")
|
asmgen.out(" $asmEndScope")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun translateSubroutine(sub: PtSub) {
|
||||||
|
asmgen.out("")
|
||||||
|
|
||||||
|
val asmStartScope: String
|
||||||
|
val asmEndScope: String
|
||||||
|
if(sub.definingBlock()!!.options.forceOutput) {
|
||||||
|
asmStartScope = ".block"
|
||||||
|
asmEndScope = ".bend"
|
||||||
} else {
|
} else {
|
||||||
// regular subroutine
|
asmStartScope = ".proc"
|
||||||
asmgen.out("${sub.name}\t$asmStartScope")
|
asmEndScope = ".pend"
|
||||||
|
|
||||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
|
||||||
require(scope.type==StNodeType.SUBROUTINE)
|
|
||||||
val varsInSubroutine = getVars(scope)
|
|
||||||
|
|
||||||
// Zeropage Variables
|
|
||||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
|
||||||
zeropagevars2asm(varnames)
|
|
||||||
|
|
||||||
// MemDefs and Consts
|
|
||||||
val mvs = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.MEMVAR }
|
|
||||||
.map { it.value as StMemVar }
|
|
||||||
val consts = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.CONSTANT }
|
|
||||||
.map { it.value as StConstant }
|
|
||||||
memdefsAndConsts2asm(mvs, consts)
|
|
||||||
|
|
||||||
asmsubs2asm(sub.statements)
|
|
||||||
|
|
||||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
|
||||||
if(sub.name=="start" && sub.definingBlock.name=="main")
|
|
||||||
entrypointInitialization()
|
|
||||||
|
|
||||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
|
||||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
|
||||||
if(sub.parameters.size==1) {
|
|
||||||
val dt = sub.parameters[0].type
|
|
||||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
|
||||||
if(dt in ByteDatatypes)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
|
||||||
else
|
|
||||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
|
||||||
} else {
|
|
||||||
require(sub.parameters.size==2)
|
|
||||||
// 2 simple byte args, first in A, second in Y
|
|
||||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
|
||||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!onlyVariables) {
|
|
||||||
asmgen.out("; statements")
|
|
||||||
sub.statements.forEach { asmgen.translate(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
asmgen.out("; variables")
|
|
||||||
val asmGenInfo = asmgen.subroutineExtra(sub)
|
|
||||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
|
||||||
if(addr!=null)
|
|
||||||
asmgen.out("$name = $addr")
|
|
||||||
else when(dt) {
|
|
||||||
DataType.UBYTE -> asmgen.out("$name .byte 0")
|
|
||||||
DataType.UWORD -> asmgen.out("$name .word 0")
|
|
||||||
else -> throw AssemblyError("weird dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
|
||||||
asmgen.out("prog8_regsaveA .byte 0")
|
|
||||||
if(asmGenInfo.usedRegsaveX)
|
|
||||||
asmgen.out("prog8_regsaveX .byte 0")
|
|
||||||
if(asmGenInfo.usedRegsaveY)
|
|
||||||
asmgen.out("prog8_regsaveY .byte 0")
|
|
||||||
if(asmGenInfo.usedFloatEvalResultVar1)
|
|
||||||
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
|
||||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
|
||||||
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
|
||||||
|
|
||||||
// normal statically allocated variables
|
|
||||||
val variables = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
|
||||||
.map { it.value as StStaticVariable }
|
|
||||||
nonZpVariables2asm(variables)
|
|
||||||
|
|
||||||
asmgen.out(" $asmEndScope\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
|
|
||||||
|
val scope = symboltable.lookupOrElse(sub.scopedName) {
|
||||||
|
throw AssemblyError("lookup ${sub.scopedName}")
|
||||||
|
}
|
||||||
|
require(scope.type==StNodeType.SUBROUTINE)
|
||||||
|
val varsInSubroutine = getVars(scope)
|
||||||
|
|
||||||
|
// Zeropage Variables
|
||||||
|
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
||||||
|
zeropagevars2asm(varnames)
|
||||||
|
|
||||||
|
// MemDefs and Consts
|
||||||
|
val mvs = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.MEMVAR }
|
||||||
|
.map { it.value as StMemVar }
|
||||||
|
val consts = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.CONSTANT }
|
||||||
|
.map { it.value as StConstant }
|
||||||
|
memdefsAndConsts2asm(mvs, consts)
|
||||||
|
|
||||||
|
asmsubs2asm(sub.children)
|
||||||
|
|
||||||
|
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||||
|
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
|
||||||
|
entrypointInitialization()
|
||||||
|
|
||||||
|
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||||
|
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||||
|
if(sub.parameters.size==1) {
|
||||||
|
val dt = sub.parameters[0].type
|
||||||
|
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||||
|
if(dt in ByteDatatypes)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||||
|
else
|
||||||
|
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||||
|
} else {
|
||||||
|
require(sub.parameters.size==2)
|
||||||
|
// 2 simple byte args, first in A, second in Y
|
||||||
|
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||||
|
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.out("; statements")
|
||||||
|
sub.children.forEach { asmgen.translate(it) }
|
||||||
|
|
||||||
|
asmgen.out("; variables")
|
||||||
|
asmgen.out(" .section BSS")
|
||||||
|
val asmGenInfo = asmgen.subroutineExtra(sub)
|
||||||
|
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||||
|
if(addr!=null)
|
||||||
|
asmgen.out("$name = $addr")
|
||||||
|
else when(dt) {
|
||||||
|
DataType.UBYTE -> asmgen.out("$name .byte ?")
|
||||||
|
DataType.UWORD -> asmgen.out("$name .word ?")
|
||||||
|
DataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
else -> throw AssemblyError("weird dt for extravar $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(asmGenInfo.usedFloatEvalResultVar1)
|
||||||
|
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||||
|
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
asmgen.out(" .send BSS")
|
||||||
|
|
||||||
|
// normal statically allocated variables
|
||||||
|
val variables = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
||||||
|
.map { it.value as StStaticVariable }
|
||||||
|
nonZpVariables2asm(variables)
|
||||||
|
|
||||||
|
asmgen.out(" $asmEndScope")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun entrypointInitialization() {
|
private fun entrypointInitialization() {
|
||||||
asmgen.out("; program startup initialization")
|
asmgen.out("; program startup initialization")
|
||||||
asmgen.out(" cld")
|
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
|
||||||
if(!options.dontReinitGlobals) {
|
// set full BSS area to zero
|
||||||
blockVariableInitializers.forEach {
|
asmgen.out("""
|
||||||
if (it.value.isNotEmpty())
|
.if prog8_bss_section_size>0
|
||||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
; reset all variables in BSS section to zero
|
||||||
}
|
lda #<prog8_bss_section_start
|
||||||
|
ldy #>prog8_bss_section_start
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldx #<prog8_bss_section_size
|
||||||
|
ldy #>prog8_bss_section_size
|
||||||
|
lda #0
|
||||||
|
jsr prog8_lib.memset
|
||||||
|
.endif""")
|
||||||
|
|
||||||
|
blockVariableInitializers.forEach {
|
||||||
|
if (it.value.isNotEmpty())
|
||||||
|
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||||
}
|
}
|
||||||
|
|
||||||
// string and array variables in zeropage that have initializer value, should be initialized
|
// string and array variables in zeropage that have initializer value, should be initialized
|
||||||
@ -397,12 +472,12 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${name}_init_value
|
lda #<${name}_init_value
|
||||||
ldy #>${name}_init_value
|
ldy #>${name}_init_value
|
||||||
sta cx16.r0L
|
sta cx16.r0
|
||||||
sty cx16.r0H
|
sty cx16.r0+1
|
||||||
lda #<${name}
|
lda #<${name}
|
||||||
ldy #>${name}
|
ldy #>${name}
|
||||||
sta cx16.r1L
|
sta cx16.r1
|
||||||
sty cx16.r1H
|
sty cx16.r1+1
|
||||||
lda #<$size
|
lda #<$size
|
||||||
ldy #>$size
|
ldy #>$size
|
||||||
jsr sys.memcopy""")
|
jsr sys.memcopy""")
|
||||||
@ -420,21 +495,19 @@ internal class ProgramAndVarsGen(
|
|||||||
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out("""+ tsx
|
asmgen.out("""+
|
||||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
|
||||||
ldx #255 ; init estack ptr
|
|
||||||
clv
|
clv
|
||||||
clc""")
|
clc""")
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ZpStringWithInitial(
|
private class ZpStringWithInitial(
|
||||||
val name: List<String>,
|
val name: String,
|
||||||
val alloc: MemoryAllocator.VarAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: Pair<String, Encoding>
|
val value: Pair<String, Encoding>
|
||||||
)
|
)
|
||||||
|
|
||||||
private class ZpArrayWithInitial(
|
private class ZpArrayWithInitial(
|
||||||
val name: List<String>,
|
val name: String,
|
||||||
val alloc: MemoryAllocator.VarAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: StArray
|
val value: StArray
|
||||||
)
|
)
|
||||||
@ -443,9 +516,10 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpStringWithInitial>()
|
val result = mutableListOf<ZpStringWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
val scopedName = variable.key
|
||||||
if(svar.onetimeInitializationStringValue!=null)
|
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||||
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
|
if(svar?.onetimeInitializationStringValue!=null)
|
||||||
|
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -454,36 +528,82 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpArrayWithInitial>()
|
val result = mutableListOf<ZpArrayWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
val scopedName = variable.key
|
||||||
if(svar.onetimeInitializationArrayValue!=null)
|
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||||
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
|
if(svar?.onetimeInitializationArrayValue!=null)
|
||||||
|
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zeropagevars2asm(varNames: Set<List<String>>) {
|
private fun zeropagevars2asm(varNames: Set<String>) {
|
||||||
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
|
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }.toList().sortedBy { it.second.address }
|
||||||
for ((scopedName, zpvar) in zpVariables) {
|
for ((scopedName, zpvar) in zpVariables) {
|
||||||
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
|
if (scopedName.startsWith("cx16.r"))
|
||||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||||
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||||
|
if(variable.dt in SplitWordArrayTypes) {
|
||||||
|
val lsbAddr = zpvar.address
|
||||||
|
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
|
||||||
|
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
|
||||||
|
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
|
||||||
|
} else {
|
||||||
|
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; non-zeropage variables")
|
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
|
||||||
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
if(varsNoInit.isNotEmpty()) {
|
||||||
stringvars.forEach {
|
asmgen.out("; non-zeropage variables without initialization value")
|
||||||
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
asmgen.out(" .section BSS")
|
||||||
|
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||||
|
uninitializedVariable2asm(it)
|
||||||
|
}
|
||||||
|
asmgen.out(" .send BSS")
|
||||||
}
|
}
|
||||||
othervars.sortedBy { it.type }.forEach {
|
|
||||||
staticVariable2asm(it)
|
if(varsWithInit.isNotEmpty()) {
|
||||||
|
asmgen.out("; non-zeropage variables")
|
||||||
|
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
|
||||||
|
stringvars.forEach {
|
||||||
|
outputStringvar(
|
||||||
|
it.name,
|
||||||
|
it.onetimeInitializationStringValue!!.second,
|
||||||
|
it.onetimeInitializationStringValue!!.first
|
||||||
|
)
|
||||||
|
}
|
||||||
|
othervars.sortedBy { it.type }.forEach {
|
||||||
|
staticVariable2asm(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uninitializedVariable2asm(variable: StStaticVariable) {
|
||||||
|
when (variable.dt) {
|
||||||
|
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
|
||||||
|
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
|
||||||
|
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
|
||||||
|
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
|
||||||
|
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
in SplitWordArrayTypes -> {
|
||||||
|
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
|
||||||
|
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
|
||||||
|
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
|
||||||
|
}
|
||||||
|
in ArrayDatatypes -> {
|
||||||
|
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
|
||||||
|
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun staticVariable2asm(variable: StStaticVariable) {
|
private fun staticVariable2asm(variable: StStaticVariable) {
|
||||||
val name = variable.name
|
|
||||||
val initialValue: Number =
|
val initialValue: Number =
|
||||||
if(variable.onetimeInitializationNumericValue!=null) {
|
if(variable.onetimeInitializationNumericValue!=null) {
|
||||||
if(variable.dt== DataType.FLOAT)
|
if(variable.dt== DataType.FLOAT)
|
||||||
@ -493,22 +613,22 @@ internal class ProgramAndVarsGen(
|
|||||||
} else 0
|
} else 0
|
||||||
|
|
||||||
when (variable.dt) {
|
when (variable.dt) {
|
||||||
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}")
|
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
|
||||||
DataType.BYTE -> asmgen.out("$name\t.char $initialValue")
|
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
|
||||||
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}")
|
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
|
||||||
DataType.WORD -> asmgen.out("$name\t.sint $initialValue")
|
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if(initialValue==0) {
|
if(initialValue==0) {
|
||||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
|
||||||
} else {
|
} else {
|
||||||
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
||||||
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
throw AssemblyError("all string vars should have been interned into prog")
|
throw AssemblyError("all string vars should have been interned into prog")
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||||
else -> {
|
else -> {
|
||||||
throw AssemblyError("weird dt")
|
throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
@ -557,6 +677,18 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out(" .sint " + chunk.joinToString())
|
asmgen.out(" .sint " + chunk.joinToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DataType.ARRAY_UW_SPLIT -> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W_SPLIT -> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||||
val floatFills = array.map {
|
val floatFills = array.map {
|
||||||
@ -579,10 +711,10 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
||||||
memvars.forEach {
|
memvars.sortedBy { it.address }.forEach {
|
||||||
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
||||||
}
|
}
|
||||||
consts.forEach {
|
consts.sortedBy { it.name }.forEach {
|
||||||
if(it.dt==DataType.FLOAT)
|
if(it.dt==DataType.FLOAT)
|
||||||
asmgen.out(" ${it.name} = ${it.value}")
|
asmgen.out(" ${it.name} = ${it.value}")
|
||||||
else
|
else
|
||||||
@ -590,12 +722,12 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun asmsubs2asm(statements: List<Statement>) {
|
private fun asmsubs2asm(statements: List<PtNode>) {
|
||||||
statements
|
statements
|
||||||
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
|
.filter { it is PtAsmSub && it.address!=null }
|
||||||
.forEach { asmsub ->
|
.forEach { asmsub ->
|
||||||
asmsub as Subroutine
|
asmsub as PtAsmSub
|
||||||
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
|
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,12 +748,12 @@ internal class ProgramAndVarsGen(
|
|||||||
val number = it.number!!.toInt()
|
val number = it.number!!.toInt()
|
||||||
"$"+number.toString(16).padStart(2, '0')
|
"$"+number.toString(16).padStart(2, '0')
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UW -> array.map {
|
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
|
||||||
if(it.number!=null) {
|
if(it.number!=null) {
|
||||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||||
}
|
}
|
||||||
else if(it.addressOf!=null) {
|
else if(it.addressOfSymbol!=null) {
|
||||||
asmgen.asmSymbolName(it.addressOf!!)
|
asmgen.asmSymbolName(it.addressOfSymbol!!)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw AssemblyError("weird array elt")
|
throw AssemblyError("weird array elt")
|
||||||
@ -648,11 +780,11 @@ internal class ProgramAndVarsGen(
|
|||||||
else
|
else
|
||||||
"-$$hexnum"
|
"-$$hexnum"
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UW -> array.map {
|
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
|
||||||
val number = it.number!!.toInt()
|
val number = it.number!!.toInt()
|
||||||
"$" + number.toString(16).padStart(4, '0')
|
"$" + number.toString(16).padStart(4, '0')
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W -> array.map {
|
DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> array.map {
|
||||||
val number = it.number!!.toInt()
|
val number = it.number!!.toInt()
|
||||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||||
if(number>=0)
|
if(number>=0)
|
||||||
|
@ -16,13 +16,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
|
|
||||||
private val zeropage = options.compTarget.machine.zeropage
|
private val zeropage = options.compTarget.machine.zeropage
|
||||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||||
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
|
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
allocateZeropageVariables()
|
allocateZeropageVariables()
|
||||||
|
zeropageVars = zeropage.allocatedVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
|
||||||
|
|
||||||
internal fun getFloatAsmConst(number: Double): String {
|
internal fun getFloatAsmConst(number: Double): String {
|
||||||
val asmName = globalFloatConsts[number]
|
val asmName = globalFloatConsts[number]
|
||||||
@ -59,7 +60,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.fold(
|
result.fold(
|
||||||
@ -67,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
numVariablesAllocatedInZP++
|
numVariablesAllocatedInZP++
|
||||||
},
|
},
|
||||||
failure = {
|
failure = {
|
||||||
errors.err(it.message!!, variable.position)
|
errors.err(it.message!!, variable.astNode.position)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||||
@ -88,7 +89,8 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
// try to allocate any other interger variables into the zeropage until it is full.
|
// try to allocate any other interger variables into the zeropage until it is full.
|
||||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
for (variable in varsDontCare.sortedBy { it.scopedName.size }) {
|
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
|
||||||
|
for (variable in sortedList) {
|
||||||
if(variable.dt in IntegerDatatypes) {
|
if(variable.dt in IntegerDatatypes) {
|
||||||
if(zeropage.free.isEmpty()) {
|
if(zeropage.free.isEmpty()) {
|
||||||
break
|
break
|
||||||
@ -97,7 +99,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||||
@ -124,6 +126,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
collect(st)
|
collect(st)
|
||||||
return vars
|
return vars.sortedBy { it.dt }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,253 @@
|
|||||||
|
package prog8.codegen.cpu6502.assignment
|
||||||
|
|
||||||
|
import prog8.code.ast.PtBinaryExpression
|
||||||
|
import prog8.code.ast.PtExpression
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.code.target.Cx16Target
|
||||||
|
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||||
|
|
||||||
|
//
|
||||||
|
// This contains codegen for stack-based evaluation of binary expressions.
|
||||||
|
// It uses the CPU stack so depth is limited.
|
||||||
|
// It is called "as a last resort" if the optimized codegen path is unable
|
||||||
|
// to come up with a special case of the expression.
|
||||||
|
//
|
||||||
|
internal class AnyExprAsmGen(
|
||||||
|
private val asmgen: AsmGen6502Internal
|
||||||
|
) {
|
||||||
|
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||||
|
when(expr.type) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes)
|
||||||
|
return assignByteBinExpr(expr, assign)
|
||||||
|
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||||
|
require(expr.operator in ComparisonOperators)
|
||||||
|
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
|
||||||
|
}
|
||||||
|
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||||
|
require(expr.operator in ComparisonOperators)
|
||||||
|
return assignFloatBinExpr(expr, assign)
|
||||||
|
}
|
||||||
|
throw AssemblyError("weird expr operand types: ${expr.left.type} and ${expr.right.type}")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||||
|
"both operands must be words"
|
||||||
|
}
|
||||||
|
return assignWordBinExpr(expr)
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||||
|
"both operands must be floats"
|
||||||
|
}
|
||||||
|
return assignFloatBinExpr(expr, assign)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird expression type in assignment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> TODO("word + at ${expr.position}")
|
||||||
|
"-" -> TODO("word - at ${expr.position}")
|
||||||
|
"*" -> TODO("word * at ${expr.position}")
|
||||||
|
"/" -> TODO("word / at ${expr.position}")
|
||||||
|
"<<" -> TODO("word << at ${expr.position}")
|
||||||
|
">>" -> TODO("word >> at ${expr.position}")
|
||||||
|
"%" -> TODO("word % at ${expr.position}")
|
||||||
|
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
|
||||||
|
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
|
||||||
|
"&" -> TODO("word and at ${expr.position}")
|
||||||
|
"|" -> TODO("word or at ${expr.position}")
|
||||||
|
"^", "xor" -> TODO("word xor at ${expr.position}")
|
||||||
|
"==" -> TODO("word == at ${expr.position}")
|
||||||
|
"!=" -> TODO("word != at ${expr.position}")
|
||||||
|
"<" -> TODO("word < at ${expr.position}")
|
||||||
|
"<=" -> TODO("word <= at ${expr.position}")
|
||||||
|
">" -> TODO("word > at ${expr.position}")
|
||||||
|
">=" -> TODO("word >= at ${expr.position}")
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||||
|
asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"-" -> {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||||
|
asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"*" -> {
|
||||||
|
TODO("byte * at ${expr.position}")
|
||||||
|
}
|
||||||
|
"/" -> {
|
||||||
|
TODO("byte / at ${expr.position}")
|
||||||
|
}
|
||||||
|
"<<" -> {
|
||||||
|
TODO("byte << at ${expr.position}")
|
||||||
|
}
|
||||||
|
">>" -> {
|
||||||
|
TODO("byte >> at ${expr.position}")
|
||||||
|
}
|
||||||
|
"%" -> {
|
||||||
|
TODO("byte % at ${expr.position}")
|
||||||
|
}
|
||||||
|
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
|
||||||
|
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
|
||||||
|
"&" -> {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||||
|
asmgen.out(" pla | and P8ZP_SCRATCH_B1")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"|" -> {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||||
|
asmgen.out(" pla | ora P8ZP_SCRATCH_B1")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"^", "xor" -> {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||||
|
asmgen.out(" pla | eor P8ZP_SCRATCH_B1")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"==" -> {
|
||||||
|
TODO("byte == at ${expr.position}")
|
||||||
|
}
|
||||||
|
"!=" -> {
|
||||||
|
TODO("byte != at ${expr.position}")
|
||||||
|
}
|
||||||
|
"<" -> {
|
||||||
|
TODO("byte < at ${expr.position}")
|
||||||
|
}
|
||||||
|
"<=" -> {
|
||||||
|
TODO("byte <= at ${expr.position}")
|
||||||
|
}
|
||||||
|
">" -> {
|
||||||
|
TODO("byte > at ${expr.position}")
|
||||||
|
}
|
||||||
|
">=" -> {
|
||||||
|
TODO("byte >= at ${expr.position}")
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {
|
||||||
|
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||||
|
asmgen.out(" jsr floats.FADDT")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"-" -> {
|
||||||
|
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||||
|
asmgen.out(" jsr floats.FSUBT")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"*" -> {
|
||||||
|
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||||
|
asmgen.out(" jsr floats.FMULTT")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"/" -> {
|
||||||
|
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||||
|
asmgen.out(" jsr floats.FDIVT")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"==" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_equal_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"!=" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_notequal_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"<" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_less_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
">" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_greater_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"<=" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_lesseq_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
">=" -> {
|
||||||
|
setupFloatComparisonFAC1vsVarAY(expr)
|
||||||
|
asmgen.out(" jsr floats.var_fac1_greatereq_f")
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else -> TODO("float expression operator ${expr.operator}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
|
||||||
|
when(asmgen.options.compTarget.name) {
|
||||||
|
C64Target.NAME -> {
|
||||||
|
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
|
||||||
|
// this requires some more forced copying around of float values in certain cases
|
||||||
|
if (right.isSimple()) {
|
||||||
|
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||||
|
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||||
|
} else {
|
||||||
|
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
|
||||||
|
asmgen.pushFAC1()
|
||||||
|
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||||
|
asmgen.popFAC2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cx16Target.NAME -> {
|
||||||
|
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||||
|
if (!right.isSimple()) asmgen.pushFAC1()
|
||||||
|
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||||
|
if (!right.isSimple()) asmgen.popFAC1()
|
||||||
|
}
|
||||||
|
else -> TODO("don't know how to evaluate float expression for selected compilation target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) {
|
||||||
|
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||||
|
if(!expr.right.isSimple()) asmgen.pushFAC1()
|
||||||
|
asmgen.assignExpressionToVariable(expr.right, "floats.floats_temp_var", DataType.FLOAT)
|
||||||
|
if(!expr.right.isSimple()) asmgen.popFAC1()
|
||||||
|
asmgen.out(" lda #<floats.floats_temp_var | ldy #>floats.floats_temp_var")
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,16 @@
|
|||||||
package prog8.codegen.cpu6502.assignment
|
package prog8.codegen.cpu6502.assignment
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.*
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.DirectMemoryWrite
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.AsmGen
|
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||||
|
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||||
|
|
||||||
|
|
||||||
internal enum class TargetStorageKind {
|
internal enum class TargetStorageKind {
|
||||||
VARIABLE,
|
VARIABLE,
|
||||||
ARRAY,
|
ARRAY,
|
||||||
MEMORY,
|
MEMORY,
|
||||||
REGISTER,
|
REGISTER
|
||||||
STACK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum class SourceStorageKind {
|
internal enum class SourceStorageKind {
|
||||||
@ -24,73 +19,69 @@ internal enum class SourceStorageKind {
|
|||||||
ARRAY,
|
ARRAY,
|
||||||
MEMORY,
|
MEMORY,
|
||||||
REGISTER,
|
REGISTER,
|
||||||
STACK, // value is already present on stack
|
|
||||||
EXPRESSION, // expression in ast-form, still to be evaluated
|
EXPRESSION, // expression in ast-form, still to be evaluated
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
val datatype: DataType,
|
val datatype: DataType,
|
||||||
val scope: Subroutine?,
|
val scope: IPtSubroutine?,
|
||||||
|
val position: Position,
|
||||||
private val variableAsmName: String? = null,
|
private val variableAsmName: String? = null,
|
||||||
val array: ArrayIndexedExpression? = null,
|
val array: PtArrayIndexer? = null,
|
||||||
val memory: DirectMemoryWrite? = null,
|
val memory: PtMemoryByte? = null,
|
||||||
val register: RegisterOrPair? = null,
|
val register: RegisterOrPair? = null,
|
||||||
val origAstTarget: AssignTarget? = null
|
val origAstTarget: PtAssignTarget? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
val constArrayIndexValue by lazy { array?.index?.asConstInteger()?.toUInt() }
|
||||||
val asmVarname: String by lazy {
|
val asmVarname: String by lazy {
|
||||||
if (array == null)
|
if (array == null)
|
||||||
variableAsmName!!
|
variableAsmName!!
|
||||||
else
|
else
|
||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var origAssign: AsmAssignment
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(register!=null && datatype !in NumericDatatypes)
|
if(register!=null && datatype !in NumericDatatypes)
|
||||||
throw AssemblyError("register must be integer or float type")
|
throw AssemblyError("register must be integer or float type")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
|
||||||
with(assign.target) {
|
with(target) {
|
||||||
val idt = inferType(program)
|
|
||||||
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when {
|
when {
|
||||||
identifier != null -> {
|
identifier != null -> {
|
||||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
val parameter = asmgen.findSubroutineParameter(identifier!!.name, asmgen)
|
||||||
if (parameter!=null) {
|
if (parameter!=null) {
|
||||||
val sub = parameter.definingSubroutine!!
|
val sub = parameter.definingAsmSub()
|
||||||
if (sub.isAsmSubroutine) {
|
if (sub!=null) {
|
||||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
val reg = sub.parameters.single { it.second===parameter }.first
|
||||||
if(reg.statusflag!=null)
|
if(reg.statusflag!=null)
|
||||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||||
else
|
else
|
||||||
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, type, definingSub, target.position, register=reg.registerOrPair, origAstTarget = this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, type, definingSub, target.position, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||||
}
|
}
|
||||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
|
||||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
|
||||||
else -> throw AssemblyError("weird target")
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
|
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, pos: Position, scope: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget =
|
||||||
when(registers) {
|
when(registers) {
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, pos, register = registers)
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers)
|
||||||
RegisterOrPair.FAC1,
|
RegisterOrPair.FAC1,
|
||||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, register = registers)
|
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
|
||||||
RegisterOrPair.R0,
|
RegisterOrPair.R0,
|
||||||
RegisterOrPair.R1,
|
RegisterOrPair.R1,
|
||||||
RegisterOrPair.R2,
|
RegisterOrPair.R2,
|
||||||
@ -106,75 +97,94 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
RegisterOrPair.R12,
|
RegisterOrPair.R12,
|
||||||
RegisterOrPair.R13,
|
RegisterOrPair.R13,
|
||||||
RegisterOrPair.R14,
|
RegisterOrPair.R14,
|
||||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSameAs(left: PtExpression): Boolean =
|
||||||
|
when(kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
val scopedName: String = if('.' in asmVarname)
|
||||||
|
asmVarname
|
||||||
|
else {
|
||||||
|
val scopeName = (scope as? PtNamedNode)?.scopedName
|
||||||
|
if (scopeName == null) asmVarname else "$scopeName.$asmVarname"
|
||||||
|
}
|
||||||
|
left is PtIdentifier && left.name==scopedName
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
left isSameAs memory!!
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||||
private val program: Program,
|
private val program: PtProgram,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
val datatype: DataType,
|
val datatype: DataType,
|
||||||
private val variableAsmName: String? = null,
|
private val variableAsmName: String? = null,
|
||||||
val array: ArrayIndexedExpression? = null,
|
val array: PtArrayIndexer? = null,
|
||||||
val memory: DirectMemoryRead? = null,
|
val memory: PtMemoryByte? = null,
|
||||||
val register: RegisterOrPair? = null,
|
val register: RegisterOrPair? = null,
|
||||||
val number: NumericLiteral? = null,
|
val number: PtNumber? = null,
|
||||||
val expression: Expression? = null
|
val expression: PtExpression? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val asmVarname: String
|
val asmVarname: String
|
||||||
get() = if(array==null)
|
get() = if(array==null)
|
||||||
variableAsmName!!
|
variableAsmName!!
|
||||||
else
|
else
|
||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.variable)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
|
||||||
val cv = value.constValue(program)
|
val cv = value as? PtNumber
|
||||||
if(cv!=null)
|
if(cv!=null)
|
||||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||||
|
|
||||||
return when(value) {
|
return when(value) {
|
||||||
is NumericLiteral -> throw AssemblyError("should have been constant value")
|
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
|
||||||
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
is PtString -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||||
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
is PtArray -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
|
||||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
if(parameter?.definingAsmSub() != null)
|
||||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
val varName=asmgen.asmVariableName(value)
|
val varName=asmgen.asmVariableName(value)
|
||||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||||
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||||
val regStr = varName.lowercase().substring(5)
|
val regStr = varName.lowercase().substring(5)
|
||||||
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
||||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg)
|
||||||
} else {
|
} else {
|
||||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = varName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DirectMemoryRead -> {
|
is PtMemoryByte -> {
|
||||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||||
}
|
}
|
||||||
is ArrayIndexedExpression -> {
|
is PtArrayIndexer -> {
|
||||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
|
||||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
|
||||||
}
|
}
|
||||||
is BuiltinFunctionCall -> {
|
is PtBuiltinFunctionCall -> {
|
||||||
val returnType = value.inferType(program)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
|
||||||
}
|
}
|
||||||
is FunctionCallExpression -> {
|
is PtFunctionCall -> {
|
||||||
val sub = value.target.targetSubroutine(program)!!
|
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
|
||||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
val sub = symbol.astNode as IPtSubroutine
|
||||||
|
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||||
|
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val returnType = value.inferType(program)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,17 +209,27 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class AsmAssignment(val source: AsmAssignSource,
|
internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
|
||||||
val target: AsmAssignTarget,
|
val target: AsmAssignTarget,
|
||||||
val isAugmentable: Boolean,
|
val memsizer: IMemSizer,
|
||||||
memsizer: IMemSizer,
|
val position: Position) {
|
||||||
val position: Position) {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
|
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
|
||||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||||
"source dt size must be less or equal to target dt size at $position"
|
"source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class AsmAssignment(source: AsmAssignSource,
|
||||||
|
target: AsmAssignTarget,
|
||||||
|
memsizer: IMemSizer,
|
||||||
|
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||||
|
|
||||||
|
internal class AsmAugmentedAssignment(source: AsmAssignSource,
|
||||||
|
val operator: String,
|
||||||
|
target: AsmAssignTarget,
|
||||||
|
memsizer: IMemSizer,
|
||||||
|
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
78
codeGenCpu6502/test/Dummies.kt
Normal file
78
codeGenCpu6502/test/Dummies.kt
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package prog8tests.codegencpu6502
|
||||||
|
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
internal object DummyMemsizer : IMemSizer {
|
||||||
|
override fun memorySize(dt: DataType) = when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
DataType.FLOAT -> 5
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||||
|
DataType.ARRAY_UW -> numElements*2
|
||||||
|
DataType.ARRAY_W -> numElements*2
|
||||||
|
DataType.ARRAY_F -> numElements*5
|
||||||
|
else -> numElements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object DummyStringEncoder : IStringEncoding {
|
||||||
|
override val defaultEncoding: Encoding = Encoding.ISO
|
||||||
|
|
||||||
|
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||||
|
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
val warnings = mutableListOf<String>()
|
||||||
|
val infos = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun err(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in errors)
|
||||||
|
errors.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun warn(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in warnings)
|
||||||
|
warnings.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun info(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in infos)
|
||||||
|
infos.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun undefined(symbol: List<String>, position: Position) {
|
||||||
|
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noErrors(): Boolean = errors.isEmpty()
|
||||||
|
|
||||||
|
override fun report() {
|
||||||
|
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||||
|
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||||
|
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||||
|
if(throwExceptionAtReportIfErrors)
|
||||||
|
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||||
|
if(!keepMessagesAfterReporting) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
errors.clear()
|
||||||
|
warnings.clear()
|
||||||
|
infos.clear()
|
||||||
|
}
|
||||||
|
}
|
129
codeGenCpu6502/test/TestCodegen.kt
Normal file
129
codeGenCpu6502/test/TestCodegen.kt
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package prog8tests.codegencpu6502
|
||||||
|
|
||||||
|
import io.kotest.assertions.throwables.shouldNotThrowAny
|
||||||
|
import io.kotest.assertions.withClue
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.SymbolTableMaker
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.codegen.cpu6502.AsmGen6502
|
||||||
|
import java.nio.file.Files
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
|
class TestCodegen: FunSpec({
|
||||||
|
|
||||||
|
fun getTestOptions(): CompilationOptions {
|
||||||
|
val target = C64Target()
|
||||||
|
return CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
zpReserved = emptyList(),
|
||||||
|
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("augmented assign on arrays") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte[] particleX = [1,2,3]
|
||||||
|
// ubyte[] particleDX = [1,2,3]
|
||||||
|
// particleX[2] += particleDX[2]
|
||||||
|
//
|
||||||
|
// word @shared xx = 1
|
||||||
|
// xx = -xx
|
||||||
|
// xx += 42
|
||||||
|
// xx += cx16.r0
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = AsmGen6502(prefixSymbols = false)
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
|
||||||
|
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val target = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||||
|
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
}
|
||||||
|
it.add(targetIdx)
|
||||||
|
}
|
||||||
|
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||||
|
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
assign.add(target)
|
||||||
|
assign.add(value)
|
||||||
|
sub.add(assign)
|
||||||
|
|
||||||
|
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||||
|
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
prefixAssign.add(prefixTarget)
|
||||||
|
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
sub.add(prefixAssign)
|
||||||
|
|
||||||
|
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
|
||||||
|
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
numberAssign.add(numberAssignTarget)
|
||||||
|
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||||
|
sub.add(numberAssign)
|
||||||
|
|
||||||
|
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
cxregAssign.add(cxregAssignTarget)
|
||||||
|
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||||
|
sub.add(cxregAssign)
|
||||||
|
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
// define the "cx16.r0" virtual register
|
||||||
|
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||||
|
program.add(cx16block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors)!!
|
||||||
|
result.name shouldBe "test"
|
||||||
|
Files.deleteIfExists(Path("${result.name}.asm"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("64tass assembler available? - if this fails you need to install 64tass version 1.58 or newer in the path") {
|
||||||
|
val command = mutableListOf("64tass", "--version")
|
||||||
|
shouldNotThrowAny {
|
||||||
|
val proc = ProcessBuilder(command).start()
|
||||||
|
val output = String(proc.inputStream.readBytes())
|
||||||
|
val result = proc.waitFor()
|
||||||
|
result.shouldBe(0)
|
||||||
|
val (_, version) = output.split('V')
|
||||||
|
val (major, minor, _) = version.split('.')
|
||||||
|
val majorNum = major.toInt()
|
||||||
|
val minorNum = minor.toInt()
|
||||||
|
withClue("64tass version should be 1.58 or newer") {
|
||||||
|
majorNum shouldBeGreaterThanOrEqual 1
|
||||||
|
if (majorNum == 1)
|
||||||
|
minorNum shouldBeGreaterThanOrEqual 58
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -29,17 +29,17 @@ dependencies {
|
|||||||
implementation project(':codeGenIntermediate')
|
implementation project(':codeGenIntermediate')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDir "${project.projectDir}/src"
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDir "${project.projectDir}/res"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,19 @@ package prog8.codegen.experimental
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.IAssemblyGenerator
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.ICodeGeneratorBackend
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.codegen.intermediate.IRCodeGen
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
import prog8.intermediate.IRFileWriter
|
import prog8.intermediate.IRFileWriter
|
||||||
|
|
||||||
class CodeGen(private val program: PtProgram,
|
class ExperiCodeGen: ICodeGeneratorBackend {
|
||||||
private val symbolTable: SymbolTable,
|
override fun generate(
|
||||||
private val options: CompilationOptions,
|
program: PtProgram,
|
||||||
private val errors: IErrorReporter
|
symbolTable: SymbolTable,
|
||||||
): IAssemblyGenerator {
|
options: CompilationOptions,
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
errors: IErrorReporter
|
||||||
|
): IAssemblyProgram {
|
||||||
|
|
||||||
// you could write a code generator directly on the PtProgram AST,
|
// you could write a code generator directly on the PtProgram AST,
|
||||||
// but you can also use the Intermediate Representation to build a codegen on:
|
// but you can also use the Intermediate Representation to build a codegen on:
|
||||||
@ -25,6 +26,15 @@ class CodeGen(private val program: PtProgram,
|
|||||||
IRFileWriter(irProgram, null).write()
|
IRFileWriter(irProgram, null).write()
|
||||||
|
|
||||||
println("** experimental codegen stub: no assembly generated **")
|
println("** experimental codegen stub: no assembly generated **")
|
||||||
return null
|
return EmptyProgram
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object EmptyProgram : IAssemblyProgram {
|
||||||
|
override val name = "<Empty Program>"
|
||||||
|
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||||
|
println("** nothing assembled **")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,18 +28,18 @@ dependencies {
|
|||||||
implementation project(':intermediate')
|
implementation project(':intermediate')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||||
|
|
||||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDir "${project.projectDir}/src"
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDir "${project.projectDir}/res"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
|
@ -3,6 +3,7 @@ package prog8.codegen.intermediate
|
|||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.AssemblyError
|
import prog8.code.core.AssemblyError
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
|
import prog8.code.core.PrefixOperators
|
||||||
import prog8.code.core.SignedDatatypes
|
import prog8.code.core.SignedDatatypes
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
@ -12,148 +13,132 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
if(assignment.target.children.single() is PtMachineRegister)
|
if(assignment.target.children.single() is PtMachineRegister)
|
||||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||||
|
|
||||||
return if (assignment.isInplaceAssign)
|
val chunks = translateRegularAssign(assignment)
|
||||||
translateInplaceAssign(assignment)
|
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
|
||||||
else
|
return chunks
|
||||||
translateRegularAssign(assignment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
|
||||||
val ident = assignment.target.identifier
|
if(augAssign.target.children.single() is PtMachineRegister)
|
||||||
val memory = assignment.target.memory
|
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||||
val array = assignment.target.array
|
|
||||||
|
|
||||||
return if(ident!=null) {
|
val ident = augAssign.target.identifier
|
||||||
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
|
val memory = augAssign.target.memory
|
||||||
|
val array = augAssign.target.array
|
||||||
|
|
||||||
|
val chunks = if(ident!=null) {
|
||||||
|
assignVarAugmented(ident.name, augAssign)
|
||||||
} else if(memory != null) {
|
} else if(memory != null) {
|
||||||
if(memory.address is PtNumber)
|
if(memory.address is PtNumber)
|
||||||
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
assignMemoryAugmented((memory.address as PtNumber).number.toInt(), augAssign)
|
||||||
else
|
else
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
} else if(array!=null) {
|
} else if(array!=null) {
|
||||||
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
||||||
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
||||||
// will be optimized later and have the double assignments removed.
|
// will be optimized later and have the double assignments removed.
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
} else {
|
} else {
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
}
|
}
|
||||||
|
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(augAssign.position)
|
||||||
|
return chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignSelfInMemoryKnownAddress(
|
private fun assignMemoryAugmented(
|
||||||
address: Int,
|
address: Int,
|
||||||
value: PtExpression,
|
assignment: PtAugmentedAssign
|
||||||
origAssign: PtAssignment
|
|
||||||
): IRCodeChunks {
|
): IRCodeChunks {
|
||||||
val vmDt = codeGen.irType(value.type)
|
val value = assignment.value
|
||||||
when(value) {
|
val targetDt = irType(assignment.target.type)
|
||||||
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
val signed = assignment.target.type in SignedDatatypes
|
||||||
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
return when(assignment.operator) {
|
||||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
"+=" -> expressionEval.operatorPlusInplace(address, null, targetDt, value)
|
||||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
"-=" -> expressionEval.operatorMinusInplace(address, null, targetDt, value)
|
||||||
is PtMemoryByte -> {
|
"*=" -> expressionEval.operatorMultiplyInplace(address, null, targetDt, value)
|
||||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
"/=" -> expressionEval.operatorDivideInplace(address, null, targetDt, signed, value)
|
||||||
emptyList() // do nothing, mem=mem null assignment.
|
"|=" -> expressionEval.operatorOrInplace(address, null, targetDt, value)
|
||||||
else {
|
"&=" -> expressionEval.operatorAndInplace(address, null, targetDt, value)
|
||||||
// read and write a (i/o) memory location to itself.
|
"^=" -> expressionEval.operatorXorInplace(address, null, targetDt, value)
|
||||||
val tempReg = codeGen.registers.nextFree()
|
"<<=" -> expressionEval.operatorShiftLeftInplace(address, null, targetDt, value)
|
||||||
val code = IRCodeChunk(null, null)
|
">>=" -> expressionEval.operatorShiftRightInplace(address, null, targetDt, signed, value)
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
"%=" -> expressionEval.operatorModuloInplace(address, null, targetDt, value)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
"==" -> expressionEval.operatorEqualsInplace(address, null, targetDt, value)
|
||||||
listOf(code)
|
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, targetDt, value)
|
||||||
}
|
"<" -> expressionEval.operatorLessInplace(address, null, targetDt, signed, value)
|
||||||
}
|
">" -> expressionEval.operatorGreaterInplace(address, null, targetDt, signed, value)
|
||||||
else -> return fallbackAssign(origAssign)
|
"<=" -> expressionEval.operatorLessEqualInplace(address, null, targetDt, signed, value)
|
||||||
|
">=" -> expressionEval.operatorGreaterEqualInplace(address, null, targetDt, signed, value)
|
||||||
|
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, address, null)
|
||||||
|
|
||||||
|
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignSelfInMemory(
|
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
|
||||||
symbol: String,
|
val value = assignment.value
|
||||||
value: PtExpression,
|
val signed = assignment.target.type in SignedDatatypes
|
||||||
origAssign: PtAssignment
|
val targetDt = irType(assignment.target.type)
|
||||||
): IRCodeChunks {
|
return when(assignment.operator) {
|
||||||
val vmDt = codeGen.irType(value.type)
|
"+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value)
|
||||||
return when(value) {
|
"-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value)
|
||||||
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, targetDt, value)
|
||||||
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
"/=" -> expressionEval.operatorDivideInplace(null, symbol, targetDt, signed, value)
|
||||||
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
"|=" -> expressionEval.operatorOrInplace(null, symbol, targetDt, value)
|
||||||
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
"or=" -> expressionEval.operatorLogicalOrInplace(null, symbol, targetDt, value)
|
||||||
is PtMemoryByte -> {
|
"&=" -> expressionEval.operatorAndInplace(null, symbol, targetDt, value)
|
||||||
val code = IRCodeChunk(null, null)
|
"and=" -> expressionEval.operatorLogicalAndInplace(null, symbol, targetDt, value)
|
||||||
val tempReg = codeGen.registers.nextFree()
|
"^=", "xor=" -> expressionEval.operatorXorInplace(null, symbol, targetDt, value)
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, targetDt, value)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, targetDt, signed, value)
|
||||||
listOf(code)
|
"%=" -> expressionEval.operatorModuloInplace(null, symbol, targetDt, value)
|
||||||
}
|
"==" -> expressionEval.operatorEqualsInplace(null, symbol, targetDt, value)
|
||||||
|
"!=" -> expressionEval.operatorNotEqualsInplace(null, symbol, targetDt, value)
|
||||||
else -> fallbackAssign(origAssign)
|
"<" -> expressionEval.operatorLessInplace(null, symbol, targetDt, signed, value)
|
||||||
|
">" -> expressionEval.operatorGreaterInplace(null, symbol, targetDt, signed, value)
|
||||||
|
"<=" -> expressionEval.operatorLessEqualInplace(null, symbol, targetDt, signed, value)
|
||||||
|
">=" -> expressionEval.operatorGreaterEqualInplace(null, symbol, targetDt, signed, value)
|
||||||
|
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, null, symbol)
|
||||||
|
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
|
||||||
if (codeGen.options.slowCodegenWarnings)
|
val value: PtExpression
|
||||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
if(origAssign.operator in PrefixOperators) {
|
||||||
return translateRegularAssign(origAssign)
|
value = PtPrefix(origAssign.operator, origAssign.target.type, origAssign.value.position)
|
||||||
}
|
value.add(origAssign.value)
|
||||||
|
|
||||||
private fun inplaceBinexpr(
|
|
||||||
operator: String,
|
|
||||||
operand: PtExpression,
|
|
||||||
vmDt: IRDataType,
|
|
||||||
signed: Boolean,
|
|
||||||
knownAddress: Int?,
|
|
||||||
symbol: String?,
|
|
||||||
origAssign: PtAssignment
|
|
||||||
): IRCodeChunks {
|
|
||||||
if(knownAddress!=null) {
|
|
||||||
when (operator) {
|
|
||||||
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
|
|
||||||
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
|
|
||||||
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
symbol!!
|
require(origAssign.operator.endsWith('='))
|
||||||
when (operator) {
|
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.target.type, origAssign.value.position)
|
||||||
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand)
|
val left: PtExpression = origAssign.target.children.single() as PtExpression
|
||||||
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand)
|
value.add(left)
|
||||||
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand)
|
value.add(origAssign.value)
|
||||||
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
|
|
||||||
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
|
|
||||||
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
|
|
||||||
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
|
|
||||||
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
|
|
||||||
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return fallbackAssign(origAssign)
|
val normalAssign = PtAssignment(origAssign.position)
|
||||||
|
normalAssign.add(origAssign.target)
|
||||||
|
normalAssign.add(value)
|
||||||
|
return translateRegularAssign(normalAssign)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
|
||||||
val code= IRCodeChunk(null, null)
|
val code= IRCodeChunk(null, null)
|
||||||
when(operator) {
|
when(operator) {
|
||||||
"+" -> { }
|
"+" -> { }
|
||||||
"-" -> {
|
"-" -> {
|
||||||
code += if(knownAddress!=null)
|
code += if(address!=null)
|
||||||
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
|
IRInstruction(Opcode.NEGM, vmDt, address = address)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
|
||||||
}
|
}
|
||||||
"~" -> {
|
"~" -> {
|
||||||
val regMask = codeGen.registers.nextFree()
|
val regMask = codeGen.registers.nextFree()
|
||||||
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
||||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, immediate = mask)
|
||||||
code += if(knownAddress!=null)
|
code += if(address!=null)
|
||||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, address = address)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird prefix operator")
|
else -> throw AssemblyError("weird prefix operator")
|
||||||
}
|
}
|
||||||
@ -162,120 +147,176 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
|
|
||||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||||
val ident = assignment.target.identifier
|
val targetIdent = assignment.target.identifier
|
||||||
val memory = assignment.target.memory
|
val targetMemory = assignment.target.memory
|
||||||
val array = assignment.target.array
|
val targetArray = assignment.target.array
|
||||||
val vmDt = codeGen.irType(assignment.value.type)
|
val valueDt = irType(assignment.value.type)
|
||||||
|
val targetDt = irType(assignment.target.type)
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
var resultRegister = -1
|
|
||||||
var resultFpRegister = -1
|
var valueRegister = -1
|
||||||
|
var valueFpRegister = -1
|
||||||
val zero = codeGen.isZero(assignment.value)
|
val zero = codeGen.isZero(assignment.value)
|
||||||
if(!zero) {
|
if(!zero) {
|
||||||
// calculate the assignment value
|
// calculate the assignment value
|
||||||
if (vmDt == IRDataType.FLOAT) {
|
if (valueDt == IRDataType.FLOAT) {
|
||||||
resultFpRegister = codeGen.registers.nextFreeFloat()
|
val tr = expressionEval.translateExpression(assignment.value)
|
||||||
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
valueFpRegister = tr.resultFpReg
|
||||||
|
addToResult(result, tr, -1, valueFpRegister)
|
||||||
} else {
|
} else {
|
||||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
val extendByteToWord = if(targetDt != valueDt) {
|
||||||
(assignment.value as PtMachineRegister).register
|
// usually an error EXCEPT when a byte is assigned to a word.
|
||||||
|
if(targetDt==IRDataType.WORD && valueDt==IRDataType.BYTE)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
throw AssemblyError("assignment value and target dt mismatch")
|
||||||
|
} else false
|
||||||
|
if (assignment.value is PtMachineRegister) {
|
||||||
|
valueRegister = (assignment.value as PtMachineRegister).register
|
||||||
|
if(extendByteToWord) {
|
||||||
|
valueRegister = codeGen.registers.nextFree()
|
||||||
|
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val reg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(assignment.value)
|
||||||
result += expressionEval.translateExpression(assignment.value, reg, -1)
|
valueRegister = tr.resultReg
|
||||||
reg
|
addToResult(result, tr, valueRegister, -1)
|
||||||
|
if(extendByteToWord) {
|
||||||
|
valueRegister = codeGen.registers.nextFree()
|
||||||
|
val opcode = if(assignment.value.type in SignedDatatypes) Opcode.EXTS else Opcode.EXT
|
||||||
|
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg), null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ident!=null) {
|
|
||||||
val symbol = ident.targetName.joinToString(".")
|
if(targetIdent!=null) {
|
||||||
val instruction = if(zero) {
|
val instruction = if(zero) {
|
||||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
|
||||||
} else {
|
} else {
|
||||||
if (vmDt == IRDataType.FLOAT)
|
if (targetDt == IRDataType.FLOAT)
|
||||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
|
||||||
}
|
}
|
||||||
result += IRCodeChunk(null, null).also { it += instruction }
|
result += IRCodeChunk(null, null).also { it += instruction }
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
else if(array!=null) {
|
else if(targetArray!=null) {
|
||||||
val variable = array.variable.targetName.joinToString(".")
|
val variable = targetArray.variable.name
|
||||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
|
||||||
|
|
||||||
if(array.variable.type==DataType.UWORD) {
|
if(targetArray.usesPointerVariable) {
|
||||||
// indexing a pointer var instead of a real array or string
|
|
||||||
if(itemsize!=1)
|
if(itemsize!=1)
|
||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||||
if(array.index.type!=DataType.UBYTE)
|
if(targetArray.index.type!=DataType.UBYTE)
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
throw AssemblyError("non-array var indexing requires bytes index")
|
||||||
val idxReg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetArray.index)
|
||||||
result += expressionEval.translateExpression(array.index, idxReg, -1)
|
val idxReg = tr.resultReg
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
val code = IRCodeChunk(null, null)
|
val code = IRCodeChunk(null, null)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
// there's no STOREZIX instruction
|
// there's no STOREZIX instruction
|
||||||
resultRegister = codeGen.registers.nextFree()
|
valueRegister = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
code += IRInstruction(Opcode.LOAD, targetDt, reg1=valueRegister, immediate = 0)
|
||||||
}
|
}
|
||||||
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
code += IRInstruction(Opcode.STOREIX, targetDt, reg1=valueRegister, reg2=idxReg, labelSymbol = variable)
|
||||||
result += code
|
result += code
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
val fixedIndex = constIntValue(array.index)
|
val fixedIndex = constIntValue(targetArray.index)
|
||||||
|
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val chunk = IRCodeChunk(null, null).also {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
|
if(targetArray.splitWords) {
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
|
||||||
|
}
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also {
|
||||||
|
if(targetArray.splitWords) {
|
||||||
|
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, immediate = arrayLength, labelSymbol = variable+"_lsb")
|
||||||
|
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, immediate = arrayLength, labelSymbol = variable+"_msb")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(vmDt== IRDataType.FLOAT) {
|
if(targetDt== IRDataType.FLOAT) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
|
val chunk = IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = variable, symbolOffset = offset)
|
||||||
|
}
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val chunk = IRCodeChunk(null, null).also {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
|
if(targetArray.splitWords) {
|
||||||
|
val msbReg = codeGen.registers.nextFree()
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
|
||||||
|
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
|
||||||
|
}
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also {
|
||||||
|
if(targetArray.splitWords) {
|
||||||
|
val msbReg = codeGen.registers.nextFree()
|
||||||
|
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = valueRegister, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
|
||||||
|
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
|
||||||
|
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = msbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
else if(memory!=null) {
|
else if(targetMemory!=null) {
|
||||||
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
|
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(memory.address is PtNumber) {
|
if(targetMemory.address is PtNumber) {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
val addressReg = tr.resultReg
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(memory.address is PtNumber) {
|
if(targetMemory.address is PtNumber) {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=(targetMemory.address as PtNumber).number.toInt()) }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
val addressReg = tr.resultReg
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,14 +326,22 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
throw AssemblyError("weird assigntarget")
|
throw AssemblyError("weird assigntarget")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
|
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int): Pair<IRCodeChunks, Int> {
|
||||||
return if(itemsize==1) {
|
// returns the code to load the Index into the register, which is also returned.
|
||||||
expressionEval.translateExpression(array.index, indexReg, -1)
|
|
||||||
} else {
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
if(itemsize==1 || array.splitWords) {
|
||||||
mult.children += array.index
|
val tr = expressionEval.translateExpression(array.index)
|
||||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
expressionEval.translateExpression(mult, indexReg, -1)
|
return Pair(result, tr.resultReg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mult: PtExpression
|
||||||
|
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||||
|
mult.children += array.index
|
||||||
|
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||||
|
val tr = expressionEval.translateExpression(mult)
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
return Pair(result, tr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,42 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||||
fun optimize() {
|
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
if(!optimizationsEnabled)
|
||||||
|
return optimizeOnlyJoinChunks()
|
||||||
|
|
||||||
|
peepholeOptimize()
|
||||||
|
val remover = IRUnusedCodeRemover(irprog, errors)
|
||||||
|
var totalRemovals = 0
|
||||||
|
do {
|
||||||
|
val numRemoved = remover.optimize()
|
||||||
|
totalRemovals += numRemoved
|
||||||
|
} while(numRemoved>0 && errors.noErrors())
|
||||||
|
errors.report()
|
||||||
|
|
||||||
|
if(totalRemovals>0) {
|
||||||
|
irprog.linkChunks() // re-link again.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeOnlyJoinChunks() {
|
||||||
|
irprog.foreachSub { sub ->
|
||||||
|
joinChunks(sub)
|
||||||
removeEmptyChunks(sub)
|
removeEmptyChunks(sub)
|
||||||
joinChunks(sub)
|
joinChunks(sub)
|
||||||
|
}
|
||||||
|
irprog.linkChunks() // re-link
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun peepholeOptimize() {
|
||||||
|
irprog.foreachSub { sub ->
|
||||||
|
joinChunks(sub)
|
||||||
|
removeEmptyChunks(sub)
|
||||||
|
joinChunks(sub)
|
||||||
|
|
||||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||||
// we don't optimize Inline Asm chunks here.
|
// we don't optimize Inline Asm chunks here.
|
||||||
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
||||||
@ -15,20 +45,39 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
val indexedInstructions = chunk1.instructions.withIndex()
|
val indexedInstructions = chunk1.instructions.withIndex()
|
||||||
.map { IndexedValue(it.index, it.value) }
|
.map { IndexedValue(it.index, it.value) }
|
||||||
val changed = removeNops(chunk1, indexedInstructions)
|
val changed = removeNops(chunk1, indexedInstructions)
|
||||||
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
|| replaceConcatZeroMsbWithExt(chunk1, indexedInstructions)
|
||||||
|
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions)
|
||||||
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||||
|
|| removeNeedlessCompares(chunk1, indexedInstructions)
|
||||||
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||||
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||||
// TODO other optimizations:
|
// TODO other optimizations
|
||||||
// more complex optimizations such as unused registers
|
|
||||||
} while (changed)
|
} while (changed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeEmptyChunks(sub)
|
removeEmptyChunks(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
irprog.linkChunks() // re-link
|
// TODO also do register optimization step here at the end?
|
||||||
|
|
||||||
|
irprog.linkChunks() // re-link
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
if (ins.opcode == Opcode.CONCAT) {
|
||||||
|
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
|
||||||
|
val prev = indexedInstructions[idx-1].value
|
||||||
|
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
|
||||||
|
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
|
||||||
|
chunk.instructions.removeAt(idx-1)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeEmptyChunks(sub: IRSubroutine) {
|
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||||
@ -38,7 +87,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
/*
|
/*
|
||||||
Empty Code chunk with label ->
|
Empty Code chunk with label ->
|
||||||
If next chunk has no label -> move label to next chunk, remove original
|
If next chunk has no label -> move label to next chunk, remove original
|
||||||
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: consolidate labels into 1)
|
If next chunk has label -> label name should be the same, remove original. Otherwise merge both labels into 1.
|
||||||
If is last chunk -> keep chunk in place because of the label.
|
If is last chunk -> keep chunk in place because of the label.
|
||||||
Empty Code chunk without label ->
|
Empty Code chunk without label ->
|
||||||
should not have been generated! ERROR.
|
should not have been generated! ERROR.
|
||||||
@ -47,6 +96,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
|
|
||||||
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
||||||
val removeChunks = mutableListOf<Int>()
|
val removeChunks = mutableListOf<Int>()
|
||||||
|
val replaceLabels = mutableMapOf<String, String>()
|
||||||
|
|
||||||
sub.chunks.withIndex().forEach { (index, chunk) ->
|
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||||
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
||||||
@ -60,10 +110,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
relabelChunks += Pair(index + 1, chunk.label!!)
|
relabelChunks += Pair(index + 1, chunk.label!!)
|
||||||
removeChunks += index
|
removeChunks += index
|
||||||
} else {
|
} else {
|
||||||
if (chunk.label == nextchunk.label)
|
// merge both labels into 1 except if this is the label chunk at the start of the subroutine
|
||||||
removeChunks += index
|
if(index>0) {
|
||||||
else {
|
if (chunk.label == nextchunk.label)
|
||||||
// TODO: consolidate labels on same chunk
|
removeChunks += index
|
||||||
|
else {
|
||||||
|
removeChunks += index
|
||||||
|
replaceLabels[chunk.label!!] = nextchunk.label!!
|
||||||
|
replaceLabels.entries.forEach { (key, value) ->
|
||||||
|
if (value == chunk.label)
|
||||||
|
replaceLabels[key] = nextchunk.label!!
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,10 +131,32 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
|
|
||||||
relabelChunks.forEach { (index, label) ->
|
relabelChunks.forEach { (index, label) ->
|
||||||
val chunk = IRCodeChunk(label, null)
|
val chunk = IRCodeChunk(label, null)
|
||||||
chunk.instructions += sub.chunks[index].instructions
|
val subChunk = sub.chunks[index]
|
||||||
|
chunk.instructions += subChunk.instructions
|
||||||
|
if(subChunk is IRCodeChunk)
|
||||||
|
chunk.appendSrcPositions(subChunk.sourceLinesPositions)
|
||||||
sub.chunks[index] = chunk
|
sub.chunks[index] = chunk
|
||||||
}
|
}
|
||||||
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||||
|
|
||||||
|
sub.chunks.forEach { chunk ->
|
||||||
|
chunk.instructions.withIndex().forEach { (idx, instr) ->
|
||||||
|
instr.labelSymbol?.let {
|
||||||
|
if(instr.opcode in OpcodesThatBranch) {
|
||||||
|
replaceLabels.forEach { (from, to) ->
|
||||||
|
if (it == from) {
|
||||||
|
chunk.instructions[idx] = instr.copy(labelSymbol = to)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val actualPrefix = "$from."
|
||||||
|
if (it.startsWith(actualPrefix))
|
||||||
|
chunk.instructions[idx] = instr.copy(labelSymbol = "$to.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun joinChunks(sub: IRSubroutine) {
|
private fun joinChunks(sub: IRSubroutine) {
|
||||||
@ -85,7 +165,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
if(sub.chunks.isEmpty())
|
if(sub.chunks.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
fun mayJoinCodeChunks(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
||||||
if(chunk.label!=null)
|
if(chunk.label!=null)
|
||||||
return false
|
return false
|
||||||
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||||
@ -102,12 +182,41 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
chunks += sub.chunks[0]
|
chunks += sub.chunks[0]
|
||||||
for(ix in 1 until sub.chunks.size) {
|
for(ix in 1 until sub.chunks.size) {
|
||||||
val lastChunk = chunks.last()
|
val lastChunk = chunks.last()
|
||||||
if(mayJoin(lastChunk, sub.chunks[ix])) {
|
val candidate = sub.chunks[ix]
|
||||||
lastChunk.instructions += sub.chunks[ix].instructions
|
when(candidate) {
|
||||||
lastChunk.next = sub.chunks[ix].next
|
is IRCodeChunk -> {
|
||||||
|
if(mayJoinCodeChunks(lastChunk, candidate)) {
|
||||||
|
lastChunk.instructions += candidate.instructions
|
||||||
|
lastChunk.next = candidate.next
|
||||||
|
if(lastChunk is IRCodeChunk)
|
||||||
|
lastChunk.appendSrcPositions(candidate.sourceLinesPositions)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
is IRInlineAsmChunk -> {
|
||||||
|
if(candidate.label!=null)
|
||||||
|
chunks += candidate
|
||||||
|
else if(lastChunk.isEmpty()) {
|
||||||
|
val label = lastChunk.label
|
||||||
|
if(label!=null)
|
||||||
|
chunks += IRInlineAsmChunk(label, candidate.assembly, candidate.isIR, candidate.next)
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IRInlineBinaryChunk -> {
|
||||||
|
if(candidate.label!=null)
|
||||||
|
chunks += candidate
|
||||||
|
else if(lastChunk.isEmpty()) {
|
||||||
|
val label = lastChunk.label
|
||||||
|
if(label!=null)
|
||||||
|
chunks += IRInlineBinaryChunk(label, candidate.data, candidate.next)
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
chunks += sub.chunks[ix]
|
|
||||||
}
|
}
|
||||||
sub.chunks.clear()
|
sub.chunks.clear()
|
||||||
sub.chunks += chunks
|
sub.chunks += chunks
|
||||||
@ -119,8 +228,8 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if(ins.opcode== Opcode.PUSH) {
|
if(ins.opcode== Opcode.PUSH) {
|
||||||
if(idx < chunk.instructions.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1]
|
||||||
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
if(insAfter.opcode == Opcode.POP) {
|
||||||
if(ins.reg1==insAfter.reg1) {
|
if(ins.reg1==insAfter.reg1) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
@ -143,16 +252,16 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
||||||
if(idx < chunk.instructions.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1]
|
||||||
if(insAfter?.opcode == ins.opcode) {
|
if(insAfter.opcode == ins.opcode) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
else if(ins.opcode== Opcode.SEC && insAfter.opcode== Opcode.CLC) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
else if(ins.opcode== Opcode.CLC && insAfter.opcode== Opcode.SEC) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@ -174,14 +283,60 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove useless RETURN
|
// remove useless RETURN
|
||||||
if(ins.opcode == Opcode.RETURN && idx>0) {
|
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR)) {
|
||||||
val previous = chunk.instructions[idx-1] as? IRInstruction
|
val previous = chunk.instructions[idx-1]
|
||||||
if(previous?.opcode in OpcodesThatJump) {
|
if(previous.opcode in OpcodesThatJump) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace subsequent opcodes that jump by just the first
|
||||||
|
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
|
||||||
|
val previous = chunk.instructions[idx-1]
|
||||||
|
if(previous.opcode in OpcodesThatJump) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace call + return --> jump
|
||||||
|
// This can no longer be done here on the IR level, with the current CALL opcode that encodes the full subroutine call setup.
|
||||||
|
// If machine code is ever generated from this IR, *that* should possibly optimize the JSR + RTS into a JMP.
|
||||||
|
// if(idx>0 && ins.opcode==Opcode.RETURN) {
|
||||||
|
// val previous = chunk.instructions[idx-1]
|
||||||
|
// if(previous.opcode==Opcode.CALL) {
|
||||||
|
// chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget)
|
||||||
|
// chunk.instructions.removeAt(idx)
|
||||||
|
// changed = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNeedlessCompares(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
// a CMPI with 0, after an instruction like LOAD that already sets the status bits, can be removed.
|
||||||
|
// but only if the instruction after it is not using the Carry bit because that won't be set by a LOAD instruction etc.
|
||||||
|
var changed = false
|
||||||
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
|
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
|
||||||
|
val previous = indexedInstructions[idx-1].value
|
||||||
|
if(previous.reg1==ins.reg1) {
|
||||||
|
if (previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
} else if (previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
|
||||||
|
val next = indexedInstructions[idx + 1].value
|
||||||
|
if (next.opcode !in OpcodesThatDependOnCarry) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
@ -192,47 +347,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
when (ins.opcode) {
|
when (ins.opcode) {
|
||||||
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
||||||
if (ins.value == 1) {
|
if (ins.immediate == 1) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.ADD, Opcode.SUB -> {
|
Opcode.ADD, Opcode.SUB -> {
|
||||||
if (ins.value == 1) {
|
if (ins.immediate == 1) {
|
||||||
chunk.instructions[idx] = IRInstruction(
|
chunk.instructions[idx] = IRInstruction(
|
||||||
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
||||||
ins.type,
|
ins.type,
|
||||||
ins.reg1
|
ins.reg1
|
||||||
)
|
)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 0) {
|
} else if (ins.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.AND -> {
|
Opcode.AND -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
} else if (ins.immediate == 255 && ins.type == IRDataType.BYTE) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
} else if (ins.immediate == 65535 && ins.type == IRDataType.WORD) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.OR -> {
|
Opcode.OR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) || (ins.immediate == 65535 && ins.type == IRDataType.WORD)) {
|
||||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = ins.immediate)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.XOR -> {
|
Opcode.XOR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@ -255,18 +410,21 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
|
return false
|
||||||
|
|
||||||
|
/*
|
||||||
var changed = false
|
var changed = false
|
||||||
indexedInstructions.forEach { (idx, ins) ->
|
indexedInstructions.forEach { (idx, ins) ->
|
||||||
|
|
||||||
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
||||||
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
||||||
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
||||||
// TODO: detect multiple sequential rnd with same reg1, only keep one
|
|
||||||
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||||
// TODO: detect multiple same ands, ors; only keep first
|
// TODO: detect multiple same ands, ors; only keep first
|
||||||
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
return changed
|
return changed
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.intermediate.IRProgram
|
||||||
|
|
||||||
|
|
||||||
|
class IRRegisterOptimizer(private val irProg: IRProgram) {
|
||||||
|
fun optimize() {
|
||||||
|
// reuseRegisters()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: this register re-use renumbering isn't going to work like this,
|
||||||
|
because subroutines will be clobbering the registers that the subroutine
|
||||||
|
which is calling them might be using...
|
||||||
|
|
||||||
|
|
||||||
|
private fun reuseRegisters() {
|
||||||
|
|
||||||
|
fun addToUsage(usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>>,
|
||||||
|
regnum: Int,
|
||||||
|
dt: IRDataType,
|
||||||
|
chunk: IRCodeChunkBase) {
|
||||||
|
val key = regnum to dt
|
||||||
|
val chunks = usage[key] ?: mutableSetOf()
|
||||||
|
chunks.add(chunk)
|
||||||
|
usage[key] = chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
val usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>> = mutableMapOf()
|
||||||
|
|
||||||
|
irProg.foreachCodeChunk { chunk ->
|
||||||
|
chunk.usedRegisters().regsTypes.forEach { (regNum, types) ->
|
||||||
|
types.forEach { dt ->
|
||||||
|
addToUsage(usage, regNum, dt, chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val registerReplacements = usage.asSequence()
|
||||||
|
.filter { it.value.size==1 }
|
||||||
|
.map { it.key to it.value.iterator().next() }
|
||||||
|
.groupBy({ it.second }, {it.first})
|
||||||
|
.asSequence()
|
||||||
|
.associate { (chunk, registers) ->
|
||||||
|
chunk to registers.withIndex().associate { (index, reg) -> reg to 50000+index }
|
||||||
|
}
|
||||||
|
|
||||||
|
registerReplacements.forEach { replaceRegisters(it.key, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceRegisters(chunk: IRCodeChunkBase, replacements: Map<Pair<Int, IRDataType>, Int>) {
|
||||||
|
val (rF, rI) = replacements.asSequence().partition { it.key.second==IRDataType.FLOAT }
|
||||||
|
val replacementsInt = rI.associate { it.key.first to it.value }
|
||||||
|
val replacementsFloat = rF.associate { it.key.first to it.value }
|
||||||
|
|
||||||
|
fun replaceRegs(fcallArgs: FunctionCallArgs?): FunctionCallArgs? {
|
||||||
|
if(fcallArgs==null)
|
||||||
|
return null
|
||||||
|
val args = if(fcallArgs.arguments.isEmpty()) fcallArgs.arguments else {
|
||||||
|
fcallArgs.arguments.map {
|
||||||
|
FunctionCallArgs.ArgumentSpec(
|
||||||
|
it.name,
|
||||||
|
it.address,
|
||||||
|
FunctionCallArgs.RegSpec(
|
||||||
|
it.reg.dt,
|
||||||
|
if(it.reg.dt==IRDataType.FLOAT)
|
||||||
|
replacementsFloat.getOrDefault(it.reg.registerNum, it.reg.registerNum)
|
||||||
|
else
|
||||||
|
replacementsInt.getOrDefault(it.reg.registerNum, it.reg.registerNum),
|
||||||
|
it.reg.cpuRegister
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val rt = fcallArgs.returns
|
||||||
|
val returns = if(rt==null) null else {
|
||||||
|
FunctionCallArgs.RegSpec(
|
||||||
|
rt.dt,
|
||||||
|
if(rt.dt==IRDataType.FLOAT)
|
||||||
|
replacementsFloat.getOrDefault(rt.registerNum, rt.registerNum)
|
||||||
|
else
|
||||||
|
replacementsInt.getOrDefault(rt.registerNum, rt.registerNum),
|
||||||
|
rt.cpuRegister
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return FunctionCallArgs(args, returns)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceRegs(instruction: IRInstruction): IRInstruction {
|
||||||
|
val reg1 = replacementsInt.getOrDefault(instruction.reg1, instruction.reg1)
|
||||||
|
val reg2 = replacementsInt.getOrDefault(instruction.reg2, instruction.reg2)
|
||||||
|
val fpReg1 = replacementsFloat.getOrDefault(instruction.fpReg1, instruction.fpReg1)
|
||||||
|
val fpReg2 = replacementsFloat.getOrDefault(instruction.fpReg2, instruction.fpReg2)
|
||||||
|
return instruction.copy(reg1 = reg1, reg2 = reg2, fpReg1 = fpReg1, fpReg2 = fpReg2, fcallArgs = replaceRegs(instruction.fcallArgs))
|
||||||
|
}
|
||||||
|
val newInstructions = chunk.instructions.map {
|
||||||
|
replaceRegs(it)
|
||||||
|
}
|
||||||
|
chunk.instructions.clear()
|
||||||
|
chunk.instructions.addAll(newInstructions)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
@ -1,38 +1,21 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
class IRUnusedCodeRemover(
|
||||||
|
private val irprog: IRProgram,
|
||||||
|
private val errors: IErrorReporter
|
||||||
|
) {
|
||||||
fun optimize(): Int {
|
fun optimize(): Int {
|
||||||
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
|
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
|
||||||
|
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
|
||||||
sub.chunks.forEach { chunk ->
|
|
||||||
chunk.label?.let { allLabeledChunks[it] = chunk }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
|
|
||||||
|
|
||||||
// remove empty subs
|
|
||||||
irprog.blocks.forEach { block ->
|
|
||||||
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
|
||||||
if(sub.isEmpty()) {
|
|
||||||
if(!sub.position.file.startsWith(libraryFilePrefix))
|
|
||||||
errors.warn("unused subroutine ${sub.label}", sub.position)
|
|
||||||
block.children.remove(sub)
|
|
||||||
numRemoved++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove empty blocks
|
// remove empty blocks
|
||||||
irprog.blocks.reversed().forEach { block ->
|
irprog.blocks.reversed().forEach { block ->
|
||||||
if(block.isEmpty()) {
|
if(block.isEmpty()) {
|
||||||
irprog.blocks.remove(block)
|
irprog.blocks.remove(block)
|
||||||
|
pruneSymboltable(block.label)
|
||||||
numRemoved++
|
numRemoved++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,17 +23,157 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
return numRemoved
|
return numRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pruneSymboltable(blockLabel: String) {
|
||||||
|
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
|
||||||
|
val prefix = "$blockLabel."
|
||||||
|
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
|
||||||
|
blockVars.forEach { stVar ->
|
||||||
|
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
|
||||||
|
chunk.instructions.forEach { ins ->
|
||||||
|
if(ins.labelSymbol == stVar.name) {
|
||||||
|
return // symbol occurs in an instruction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irprog.st.allVariables().forEach { variable ->
|
||||||
|
val initValue = variable.onetimeInitializationArrayValue
|
||||||
|
if(!initValue.isNullOrEmpty()) {
|
||||||
|
if(initValue.any {
|
||||||
|
it.addressOfSymbol?.startsWith(blockLabel)==true
|
||||||
|
})
|
||||||
|
return // symbol occurs in an initializer value (address-of this symbol)_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irprog.st.removeTree(blockLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnusedSubroutines(): Int {
|
||||||
|
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
|
||||||
|
irprog.foreachCodeChunk { chunk ->
|
||||||
|
chunk.label?.let { allLabeledChunks[it] = chunk }
|
||||||
|
}
|
||||||
|
|
||||||
|
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
|
||||||
|
irprog.blocks.forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||||
|
if(sub.isEmpty()) {
|
||||||
|
if(!block.options.ignoreUnused) {
|
||||||
|
errors.info("unused subroutine '${sub.label}'", sub.position)
|
||||||
|
}
|
||||||
|
block.children.remove(sub)
|
||||||
|
irprog.st.removeTree(sub.label)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnusedAsmSubroutines(): Int {
|
||||||
|
val allLabeledAsmsubs = irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRAsmSubroutine>() }
|
||||||
|
.associateBy { it.label }
|
||||||
|
|
||||||
|
var numRemoved = removeSimpleUnlinkedAsmsubs(allLabeledAsmsubs)
|
||||||
|
irprog.blocks.forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRAsmSubroutine>().reversed().forEach { sub ->
|
||||||
|
if(sub.isEmpty()) {
|
||||||
|
if(!block.options.ignoreUnused) {
|
||||||
|
errors.info("unused subroutine '${sub.label}'", sub.position)
|
||||||
|
}
|
||||||
|
block.children.remove(sub)
|
||||||
|
irprog.st.removeTree(sub.label)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSimpleUnlinkedAsmsubs(allSubs: Map<String, IRAsmSubroutine>): Int {
|
||||||
|
val linkedAsmSubs = mutableSetOf<IRAsmSubroutine>()
|
||||||
|
|
||||||
|
// TODO: asmsubs in library modules are never removed, we can't really tell here if they're actually being called or not...
|
||||||
|
|
||||||
|
// check if asmsub is called from another asmsub
|
||||||
|
irprog.blocks.asSequence().forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRAsmSubroutine>().forEach { sub ->
|
||||||
|
if (block.options.forceOutput || block.library)
|
||||||
|
linkedAsmSubs += sub
|
||||||
|
if (sub.asmChunk.isNotEmpty()) {
|
||||||
|
allSubs.forEach { (label, asmsub) ->
|
||||||
|
if (sub.asmChunk.assembly.contains(label))
|
||||||
|
linkedAsmSubs += asmsub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val inlineAsm = sub.asmChunk.next as? IRInlineAsmChunk
|
||||||
|
if(inlineAsm!=null) {
|
||||||
|
allSubs.forEach { (label, asmsub) ->
|
||||||
|
if (inlineAsm.assembly.contains(label))
|
||||||
|
linkedAsmSubs += asmsub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedAsmsubs(linkedAsmSubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnlinkedAsmsubs(linkedAsmSubs: Set<IRAsmSubroutine>): Int {
|
||||||
|
var numRemoved = 0
|
||||||
|
irprog.blocks.asSequence().forEach { block ->
|
||||||
|
block.children.withIndex().reversed().forEach { (index, child) ->
|
||||||
|
if(child is IRAsmSubroutine && child !in linkedAsmSubs) {
|
||||||
|
block.children.removeAt(index)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||||
val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
|
val entrypointSub = irprog.blocks.single { it.label=="main" }
|
||||||
|
.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||||
|
|
||||||
|
// all chunks referenced in array initializer values are also 'reachable':
|
||||||
|
irprog.st.allVariables()
|
||||||
|
.filter { !it.uninitialized }
|
||||||
|
.forEach {
|
||||||
|
it.onetimeInitializationArrayValue?.let { array ->
|
||||||
|
array.forEach {elt ->
|
||||||
|
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||||
|
reachable.add(irprog.getChunkWithLabel(elt.addressOfSymbol!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun grow() {
|
fun grow() {
|
||||||
val new = mutableSetOf<IRCodeChunkBase>()
|
val new = mutableSetOf<IRCodeChunkBase>()
|
||||||
reachable.forEach {
|
reachable.forEach {
|
||||||
it.next?.let { next -> new += next }
|
it.next?.let { next -> new += next }
|
||||||
it.instructions.forEach { instr ->
|
it.instructions.forEach { instr ->
|
||||||
if (instr.branchTarget == null)
|
if (instr.branchTarget == null)
|
||||||
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
|
instr.labelSymbol?.let { label ->
|
||||||
|
val chunk = allLabeledChunks[label.substringBeforeLast('.')]
|
||||||
|
if(chunk!=null)
|
||||||
|
new+=chunk
|
||||||
|
else
|
||||||
|
allLabeledChunks[label]?.let { new += it }
|
||||||
|
}
|
||||||
else
|
else
|
||||||
new += instr.branchTarget!!
|
new += instr.branchTarget!!
|
||||||
}
|
}
|
||||||
@ -72,29 +195,47 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
||||||
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||||
|
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
// all chunks referenced in array initializer values are linked as well!:
|
||||||
sub.chunks.forEach { chunk ->
|
irprog.st.allVariables()
|
||||||
chunk.next?.let { next -> linkedChunks += next }
|
.filter { !it.uninitialized }
|
||||||
chunk.instructions.forEach {
|
.forEach {
|
||||||
if(it.branchTarget==null) {
|
it.onetimeInitializationArrayValue?.let { array ->
|
||||||
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
array.forEach {elt ->
|
||||||
} else {
|
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||||
linkedChunks += it.branchTarget!!
|
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chunk.label == "main.start")
|
}
|
||||||
linkedChunks += chunk
|
|
||||||
|
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 } }
|
||||||
|
} else {
|
||||||
|
linkedChunks += it.branchTarget!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chunk.label == "main.start")
|
||||||
|
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('.')
|
||||||
|
allLabeledChunks[chunkName]?.let { linkedChunks+=it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return removeUnlinkedChunks(linkedChunks)
|
return removeUnlinkedChunks(linkedChunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeUnlinkedChunks(
|
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||||
linkedChunks: MutableSet<IRCodeChunkBase>
|
|
||||||
): Int {
|
|
||||||
var numRemoved = 0
|
var numRemoved = 0
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
irprog.foreachSub { sub ->
|
||||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||||
if (chunk !in linkedChunks) {
|
if (chunk !in linkedChunks) {
|
||||||
if (chunk === sub.chunks[0]) {
|
if (chunk === sub.chunks[0]) {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.intermediate.SyscallRegisterBase
|
|
||||||
|
|
||||||
internal class RegisterPool {
|
internal class RegisterPool {
|
||||||
// reserve 0,1,2 for return values of subroutine calls and syscalls
|
// reserve 0,1,2 for return values of subroutine calls and syscalls in IR assembly code
|
||||||
private var firstFree: Int=3
|
private var firstFree: Int=3
|
||||||
private var firstFreeFloat: Int=3
|
private var firstFreeFloat: Int=3
|
||||||
|
|
||||||
@ -14,16 +11,12 @@ internal class RegisterPool {
|
|||||||
fun nextFree(): Int {
|
fun nextFree(): Int {
|
||||||
val result = firstFree
|
val result = firstFree
|
||||||
firstFree++
|
firstFree++
|
||||||
if(firstFree >= SyscallRegisterBase)
|
|
||||||
throw AssemblyError("out of virtual registers (int)")
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextFreeFloat(): Int {
|
fun nextFreeFloat(): Int {
|
||||||
val result = firstFreeFloat
|
val result = firstFreeFloat
|
||||||
firstFreeFloat++
|
firstFreeFloat++
|
||||||
if(firstFreeFloat >= SyscallRegisterBase)
|
|
||||||
throw AssemblyError("out of virtual registers (fp)")
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,30 @@ package prog8.codegen.vm
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.IAssemblyGenerator
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.ICodeGeneratorBackend
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.codegen.intermediate.IRCodeGen
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
import prog8.intermediate.IRFileWriter
|
import prog8.intermediate.IRFileWriter
|
||||||
import prog8.intermediate.IRProgram
|
import prog8.intermediate.IRProgram
|
||||||
|
|
||||||
class VmCodeGen(private val program: PtProgram,
|
class VmCodeGen: ICodeGeneratorBackend {
|
||||||
private val symbolTable: SymbolTable,
|
override fun generate(
|
||||||
private val options: CompilationOptions,
|
program: PtProgram,
|
||||||
private val errors: IErrorReporter
|
symbolTable: SymbolTable,
|
||||||
): IAssemblyGenerator {
|
options: CompilationOptions,
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
errors: IErrorReporter
|
||||||
|
): IAssemblyProgram {
|
||||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
val irProgram = irCodeGen.generate()
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||||
// the VM reads the IR file from disk.
|
// the VM reads the IR file from disk.
|
||||||
IRFileWriter(irProgram, null).write()
|
IRFileWriter(irProgram, null).write()
|
||||||
return true
|
return true
|
||||||
|
@ -1,20 +1,76 @@
|
|||||||
import prog8.code.core.DataType
|
import prog8.code.core.*
|
||||||
import prog8.code.core.Encoding
|
|
||||||
import prog8.code.core.IMemSizer
|
|
||||||
import prog8.code.core.IStringEncoding
|
|
||||||
|
|
||||||
|
|
||||||
internal object DummyMemsizer : IMemSizer {
|
internal object DummyMemsizer : IMemSizer {
|
||||||
override fun memorySize(dt: DataType) = 0
|
override fun memorySize(dt: DataType) = when(dt) {
|
||||||
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
|
in ByteDatatypes -> 1
|
||||||
|
DataType.FLOAT -> 5
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||||
|
DataType.ARRAY_UW -> numElements*2
|
||||||
|
DataType.ARRAY_W -> numElements*2
|
||||||
|
DataType.ARRAY_F -> numElements*5
|
||||||
|
else -> numElements
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object DummyStringEncoder : IStringEncoding {
|
internal object DummyStringEncoder : IStringEncoding {
|
||||||
|
override val defaultEncoding: Encoding = Encoding.ISO
|
||||||
|
|
||||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||||
|
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
val warnings = mutableListOf<String>()
|
||||||
|
val infos = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun err(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in errors)
|
||||||
|
errors.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun warn(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in warnings)
|
||||||
|
warnings.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun info(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in infos)
|
||||||
|
infos.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun undefined(symbol: List<String>, position: Position) {
|
||||||
|
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noErrors(): Boolean = errors.isEmpty()
|
||||||
|
|
||||||
|
override fun report() {
|
||||||
|
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||||
|
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||||
|
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||||
|
if(throwExceptionAtReportIfErrors)
|
||||||
|
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||||
|
if(!keepMessagesAfterReporting) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
errors.clear()
|
||||||
|
warnings.clear()
|
||||||
|
infos.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import prog8.intermediate.*
|
|||||||
class TestIRPeepholeOpt: FunSpec({
|
class TestIRPeepholeOpt: FunSpec({
|
||||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||||
require(chunks.first().label=="main.start")
|
require(chunks.first().label=="main.start")
|
||||||
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||||
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||||
chunks.forEach { sub += it }
|
chunks.forEach { sub += it }
|
||||||
block += sub
|
block += sub
|
||||||
@ -18,12 +18,13 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
CbmPrgLauncherType.NONE,
|
CbmPrgLauncherType.NONE,
|
||||||
ZeropageType.DONTUSE,
|
ZeropageType.DONTUSE,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
CompilationOptions.AllZeropageAllowed,
|
||||||
floats = false,
|
floats = false,
|
||||||
noSysInit = true,
|
noSysInit = true,
|
||||||
compTarget = target,
|
compTarget = target,
|
||||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
)
|
)
|
||||||
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
val prog = IRProgram("test", IRSymbolTable(), options, target)
|
||||||
prog.addBlock(block)
|
prog.addBlock(block)
|
||||||
prog.linkChunks()
|
prog.linkChunks()
|
||||||
prog.validate()
|
prog.validate()
|
||||||
@ -36,25 +37,25 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
return makeIRProgram(listOf(chunk))
|
return makeIRProgram(listOf(chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
|
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.allSubs().flatMap { it.chunks }.toList()
|
||||||
|
|
||||||
test("remove nops") {
|
test("remove nops") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
|
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42),
|
||||||
IRInstruction(Opcode.NOP),
|
IRInstruction(Opcode.NOP),
|
||||||
IRInstruction(Opcode.NOP)
|
IRInstruction(Opcode.NOP)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 3
|
irProg.chunks().single().instructions.size shouldBe 3
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 1
|
irProg.chunks().single().instructions.size shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remove jmp to label below") {
|
test("remove jmp to label below") {
|
||||||
val c1 = IRCodeChunk("main.start", null)
|
val c1 = IRCodeChunk("main.start", null)
|
||||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
|
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
|
||||||
val c2 = IRCodeChunk("label", null)
|
val c2 = IRCodeChunk("label", null)
|
||||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
|
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
|
||||||
c2 += IRInstruction(Opcode.NOP) // removed
|
c2 += IRInstruction(Opcode.NOP) // removed
|
||||||
val c3 = IRCodeChunk("label2", null)
|
val c3 = IRCodeChunk("label2", null)
|
||||||
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
||||||
@ -65,16 +66,14 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
irProg.chunks().size shouldBe 4
|
irProg.chunks().size shouldBe 4
|
||||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().size shouldBe 4
|
irProg.chunks().size shouldBe 3
|
||||||
irProg.chunks()[0].label shouldBe "main.start"
|
irProg.chunks()[0].label shouldBe "main.start"
|
||||||
irProg.chunks()[1].label shouldBe "label"
|
irProg.chunks()[1].label shouldBe "label2"
|
||||||
irProg.chunks()[2].label shouldBe "label2"
|
irProg.chunks()[2].label shouldBe "label3"
|
||||||
irProg.chunks()[3].label shouldBe "label3"
|
|
||||||
irProg.chunks()[0].isEmpty() shouldBe true
|
irProg.chunks()[0].isEmpty() shouldBe true
|
||||||
irProg.chunks()[1].isEmpty() shouldBe true
|
irProg.chunks()[1].isEmpty() shouldBe false
|
||||||
irProg.chunks()[2].isEmpty() shouldBe false
|
irProg.chunks()[2].isEmpty() shouldBe true
|
||||||
irProg.chunks()[3].isEmpty() shouldBe true
|
|
||||||
val instr = irProg.chunks().flatMap { it.instructions }
|
val instr = irProg.chunks().flatMap { it.instructions }
|
||||||
instr.size shouldBe 2
|
instr.size shouldBe 2
|
||||||
instr[0].opcode shouldBe Opcode.JUMP
|
instr[0].opcode shouldBe Opcode.JUMP
|
||||||
@ -92,7 +91,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 6
|
irProg.chunks().single().instructions.size shouldBe 6
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
instr[0].opcode shouldBe Opcode.CLC
|
instr[0].opcode shouldBe Opcode.CLC
|
||||||
@ -107,7 +106,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
instr[0].opcode shouldBe Opcode.LOADR
|
instr[0].opcode shouldBe Opcode.LOADR
|
||||||
@ -117,31 +116,31 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
|
|
||||||
test("remove useless div/mul, add/sub") {
|
test("remove useless div/mul, add/sub") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 10
|
irProg.chunks().single().instructions.size shouldBe 10
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
}
|
}
|
||||||
|
|
||||||
test("replace add/sub 1 by inc/dec") {
|
test("replace add/sub 1 by inc/dec") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 2
|
irProg.chunks().single().instructions.size shouldBe 2
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 2
|
instr.size shouldBe 2
|
||||||
instr[0].opcode shouldBe Opcode.INC
|
instr[0].opcode shouldBe Opcode.INC
|
||||||
@ -150,40 +149,40 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
|
|
||||||
test("remove useless and/or/xor") {
|
test("remove useless and/or/xor") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 65535),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 200),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 60000),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 8
|
irProg.chunks().single().instructions.size shouldBe 8
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
}
|
}
|
||||||
|
|
||||||
test("replace and/or/xor by constant number") {
|
test("replace and/or/xor by constant number") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 4
|
instr.size shouldBe 4
|
||||||
instr[0].opcode shouldBe Opcode.LOAD
|
instr[0].opcode shouldBe Opcode.LOAD
|
||||||
instr[1].opcode shouldBe Opcode.LOAD
|
instr[1].opcode shouldBe Opcode.LOAD
|
||||||
instr[2].opcode shouldBe Opcode.LOAD
|
instr[2].opcode shouldBe Opcode.LOAD
|
||||||
instr[3].opcode shouldBe Opcode.LOAD
|
instr[3].opcode shouldBe Opcode.LOAD
|
||||||
instr[0].value shouldBe 0
|
instr[0].immediate shouldBe 0
|
||||||
instr[1].value shouldBe 0
|
instr[1].immediate shouldBe 0
|
||||||
instr[2].value shouldBe 255
|
instr[2].immediate shouldBe 255
|
||||||
instr[3].value shouldBe 65535
|
instr[3].immediate shouldBe 65535
|
||||||
}
|
}
|
||||||
})
|
})
|
478
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
478
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.SymbolTableMaker
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import prog8.codegen.vm.VmAssemblyProgram
|
||||||
|
import prog8.codegen.vm.VmCodeGen
|
||||||
|
import prog8.intermediate.IRSubroutine
|
||||||
|
import prog8.intermediate.Opcode
|
||||||
|
|
||||||
|
class TestVmCodeGen: FunSpec({
|
||||||
|
|
||||||
|
fun getTestOptions(): CompilationOptions {
|
||||||
|
val target = VMTarget()
|
||||||
|
return CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
zpReserved = emptyList(),
|
||||||
|
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("augmented assigns") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte[] particleX = [1,2,3]
|
||||||
|
// ubyte[] particleDX = [1,2,3]
|
||||||
|
// particleX[2] += particleDX[2]
|
||||||
|
//
|
||||||
|
// word @shared xx = 1
|
||||||
|
// xx = -xx
|
||||||
|
// xx += 42
|
||||||
|
// xx += cx16.r0
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
|
||||||
|
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val target = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||||
|
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
}
|
||||||
|
it.add(targetIdx)
|
||||||
|
}
|
||||||
|
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||||
|
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
assign.add(target)
|
||||||
|
assign.add(value)
|
||||||
|
sub.add(assign)
|
||||||
|
|
||||||
|
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||||
|
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
prefixAssign.add(prefixTarget)
|
||||||
|
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
sub.add(prefixAssign)
|
||||||
|
|
||||||
|
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
numberAssign.add(numberAssignTarget)
|
||||||
|
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||||
|
sub.add(numberAssign)
|
||||||
|
|
||||||
|
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
cxregAssign.add(cxregAssignTarget)
|
||||||
|
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||||
|
sub.add(cxregAssign)
|
||||||
|
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
// define the "cx16.r0" virtual register
|
||||||
|
val cx16block = PtBlock("cx16", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||||
|
program.add(cx16block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float comparison expressions against zero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==0
|
||||||
|
// nop
|
||||||
|
// if f1!=0
|
||||||
|
// nop
|
||||||
|
// if f1>0
|
||||||
|
// nop
|
||||||
|
// if f1<0
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float comparison expressions against nonzero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==42
|
||||||
|
// nop
|
||||||
|
// if f1!=42
|
||||||
|
// nop
|
||||||
|
// if f1>42
|
||||||
|
// nop
|
||||||
|
// if f1<42
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float conditional jump") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==42
|
||||||
|
// goto $c000
|
||||||
|
// if f1>42
|
||||||
|
// goto $c000
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup())
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup())
|
||||||
|
sub.add(if2)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer comparison expressions against zero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// byte @shared sb1
|
||||||
|
//
|
||||||
|
// if sb1==0
|
||||||
|
// nop
|
||||||
|
// if sb1!=0
|
||||||
|
// nop
|
||||||
|
// if sb1>0
|
||||||
|
// nop
|
||||||
|
// if sb1<0
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer comparison expressions against nonzero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// byte @shared sb1
|
||||||
|
//
|
||||||
|
// if sb1==42
|
||||||
|
// nop
|
||||||
|
// if sb1!=42
|
||||||
|
// nop
|
||||||
|
// if sb1>42
|
||||||
|
// nop
|
||||||
|
// if sb1<42
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer conditional jump") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte @shared ub1
|
||||||
|
//
|
||||||
|
// if ub1==42
|
||||||
|
// goto $c000
|
||||||
|
// if ub1>42
|
||||||
|
// goto $c000
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup())
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup())
|
||||||
|
sub.add(if2)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("romsub allowed in ir-codegen") {
|
||||||
|
//main {
|
||||||
|
// romsub $5000 = routine()
|
||||||
|
//
|
||||||
|
// sub start() {
|
||||||
|
// routine()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||||
|
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||||
|
block.add(romsub)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
|
||||||
|
sub.add(call)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
irChunks[0].instructions.size shouldBe 2
|
||||||
|
val preparecallInstr = irChunks[0].instructions[0]
|
||||||
|
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
|
||||||
|
preparecallInstr.immediate shouldBe 0
|
||||||
|
val callInstr = irChunks[0].instructions[1]
|
||||||
|
callInstr.opcode shouldBe Opcode.CALL
|
||||||
|
callInstr.address shouldBe 0x5000
|
||||||
|
}
|
||||||
|
})
|
8
codeGenVirtual/codeGenVirtual.iml
Normal file
8
codeGenVirtual/codeGenVirtual.iml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -27,16 +27,17 @@ dependencies {
|
|||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/src"]
|
srcDir "${project.projectDir}/src"
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDir "${project.projectDir}/res"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,5 +11,6 @@
|
|||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -1,125 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.IStatementContainer
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.BinaryExpression
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.TypecastExpression
|
|
||||||
import prog8.ast.getTempVar
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.AssignmentOrigin
|
|
||||||
import prog8.ast.walk.AstWalker
|
|
||||||
import prog8.ast.walk.IAstModification
|
|
||||||
import prog8.code.core.AugmentAssignmentOperators
|
|
||||||
import prog8.code.core.CompilationOptions
|
|
||||||
import prog8.code.core.DataType
|
|
||||||
import prog8.code.target.VMTarget
|
|
||||||
|
|
||||||
|
|
||||||
class BinExprSplitter(private val program: Program, private val options: CompilationOptions) : AstWalker() {
|
|
||||||
|
|
||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
|
||||||
|
|
||||||
if(options.compTarget.name == VMTarget.NAME)
|
|
||||||
return noModifications // don't split expressions when targeting the vm codegen, it handles nested expressions well
|
|
||||||
|
|
||||||
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
|
||||||
return noModifications
|
|
||||||
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
|
||||||
if (binExpr != null) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
|
||||||
by attempting to splitting it up into individual simple steps.
|
|
||||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
|
||||||
|
|
||||||
|
|
||||||
X = BinExpr X = LeftExpr
|
|
||||||
<operator> followed by
|
|
||||||
/ \ IF 'X' not used X = BinExpr
|
|
||||||
/ \ IN expression ==> <operator>
|
|
||||||
/ \ / \
|
|
||||||
LeftExpr. RightExpr. / \
|
|
||||||
X RightExpr.
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
|
||||||
if(assignment.target isSameAs binExpr.right)
|
|
||||||
return noModifications
|
|
||||||
if(assignment.target isSameAs binExpr.left) {
|
|
||||||
if(binExpr.right.isSimple)
|
|
||||||
return noModifications
|
|
||||||
val leftBx = binExpr.left as? BinaryExpression
|
|
||||||
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
|
|
||||||
return noModifications
|
|
||||||
val rightBx = binExpr.right as? BinaryExpression
|
|
||||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
|
||||||
return noModifications
|
|
||||||
|
|
||||||
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
|
|
||||||
// val dt = assignment.target.inferType(program)
|
|
||||||
// if(!dt.isInteger)
|
|
||||||
// return noModifications
|
|
||||||
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
|
|
||||||
// val assignTempVar = Assignment(
|
|
||||||
// AssignTarget(tempVar, null, null, binExpr.right.position),
|
|
||||||
// binExpr.right, binExpr.right.position
|
|
||||||
// )
|
|
||||||
// return listOf(
|
|
||||||
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
|
|
||||||
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
if(binExpr.right.isSimple) {
|
|
||||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, AssignmentOrigin.OPTIMIZER, binExpr.left.position)
|
|
||||||
val targetExpr = assignment.target.toExpression()
|
|
||||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
|
||||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO further unraveling of binary expression trees into flat statements.
|
|
||||||
// however this should probably be done in a more generic way to also work on
|
|
||||||
// the expressiontrees that are not used in an assignment statement...
|
|
||||||
}
|
|
||||||
|
|
||||||
val typecast = assignment.value as? TypecastExpression
|
|
||||||
if(typecast!=null) {
|
|
||||||
val origExpr = typecast.expression as? BinaryExpression
|
|
||||||
if(origExpr!=null && options.compTarget.name!=VMTarget.NAME) {
|
|
||||||
// it's a typecast of a binary expression.
|
|
||||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
|
||||||
// (that has the type of the expression), and then finally doing the typecast.
|
|
||||||
// Once it's outside the typecast, the regular splitting can commence.
|
|
||||||
val tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
val (tempVarName, _) = program.getTempVar(tempvarDt)
|
|
||||||
val assignTempVar = Assignment(
|
|
||||||
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
|
|
||||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
|
||||||
)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVarName, typecast.position), typecast)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isSimpleTarget(target: AssignTarget) =
|
|
||||||
if (target.identifier!=null || target.memoryAddress!=null)
|
|
||||||
!target.isIOAddress(options.compTarget.machine)
|
|
||||||
else
|
|
||||||
false
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user